More refactoring and docs as suggested by golint

master
Michael Dvorkin 11 years ago
parent f1548e6bac
commit 3b9b0745de
  1. 26
      column_editor.go
  2. 153
      layout.go
  3. 8
      profile.go

@ -10,9 +10,10 @@ import `github.com/michaeldv/termbox-go`
// current column name in the header, then waits for arrow keys (choose // current column name in the header, then waits for arrow keys (choose
// another column), Enter (reverse sort order), or Esc (exit). // another column), Enter (reverse sort order), or Esc (exit).
type ColumnEditor struct { type ColumnEditor struct {
screen *Screen // Pointer to Screen so we could use screen.Draw(). screen *Screen // Pointer to Screen so we could use screen.Draw().
quotes *Quotes // Pointer to Quotes to redraw them when the sort order changes. quotes *Quotes // Pointer to Quotes to redraw them when the sort order changes.
profile *Profile // Pointer to Profile where we save newly selected sort order. layout *Layout // Pointer to Layout to redraw stock quotes header.
profile *Profile // Pointer to Profile where we save newly selected sort order.
} }
// Initialize sets internal variables and highlights current column name // Initialize sets internal variables and highlights current column name
@ -20,6 +21,7 @@ type ColumnEditor struct {
func (editor *ColumnEditor) Initialize(screen *Screen, quotes *Quotes) *ColumnEditor { func (editor *ColumnEditor) Initialize(screen *Screen, quotes *Quotes) *ColumnEditor {
editor.screen = screen editor.screen = screen
editor.quotes = quotes editor.quotes = quotes
editor.layout = screen.layout
editor.profile = quotes.profile editor.profile = quotes.profile
editor.selectCurrentColumn() editor.selectCurrentColumn()
@ -51,25 +53,25 @@ func (editor *ColumnEditor) Handle(event termbox.Event) bool {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *ColumnEditor) selectCurrentColumn() *ColumnEditor { func (editor *ColumnEditor) selectCurrentColumn() *ColumnEditor {
editor.profile.selected_column = editor.profile.SortColumn editor.profile.selectedColumn = editor.profile.SortColumn
editor.redrawHeader() editor.redrawHeader()
return editor return editor
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *ColumnEditor) selectLeftColumn() *ColumnEditor { func (editor *ColumnEditor) selectLeftColumn() *ColumnEditor {
editor.profile.selected_column-- editor.profile.selectedColumn--
if editor.profile.selected_column < 0 { if editor.profile.selectedColumn < 0 {
editor.profile.selected_column = TotalColumns - 1 editor.profile.selectedColumn = editor.layout.TotalColumns() - 1
} }
return editor return editor
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *ColumnEditor) selectRightColumn() *ColumnEditor { func (editor *ColumnEditor) selectRightColumn() *ColumnEditor {
editor.profile.selected_column++ editor.profile.selectedColumn++
if editor.profile.selected_column > TotalColumns - 1 { if editor.profile.selectedColumn > editor.layout.TotalColumns() - 1 {
editor.profile.selected_column = 0 editor.profile.selectedColumn = 0
} }
return editor return editor
} }
@ -85,13 +87,13 @@ func (editor *ColumnEditor) execute() *ColumnEditor {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *ColumnEditor) done() bool { func (editor *ColumnEditor) done() bool {
editor.profile.selected_column = -1 editor.profile.selectedColumn = -1
return true return true
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *ColumnEditor) redrawHeader() { func (editor *ColumnEditor) redrawHeader() {
editor.screen.DrawLine(0, 4, editor.screen.layout.Header(editor.profile)) editor.screen.DrawLine(0, 4, editor.layout.Header(editor.profile))
termbox.Flush() termbox.Flush()
} }

@ -1,6 +1,6 @@
// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. // Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style license that can
// license that can be found in the LICENSE file. // be found in the LICENSE file.
package mop package mop
@ -14,91 +14,100 @@ import (
`time` `time`
) )
const TotalColumns = 15 // Column describes formatting rules for individual column within the list
// of stock quotes.
type Column struct { type Column struct {
width int width int // Column width.
name string name string // The name of the field in the Stock struct.
title string title string // Column title to display in the header.
formatter func(string)string formatter func(string)string // Optional function to format the contents of the column.
} }
// Layout is used to format and display all the collected data, i.e. market
// updates and the list of stock quotes.
type Layout struct { type Layout struct {
columns []Column columns []Column // List of stock quotes columns.
sorter *Sorter sorter *Sorter // Pointer to sorting receiver.
regex *regexp.Regexp regex *regexp.Regexp // Pointer to regular expression to align decimal points.
market_template *template.Template marketTemplate *template.Template // Pointer to template to format market data.
quotes_template *template.Template quotesTemplate *template.Template // Pointer to template to format the list of stock quotes.
} }
//----------------------------------------------------------------------------- // Initialize assigns the default values that stay unchanged for the life of
func (self *Layout) Initialize() *Layout { // allocated Layout struct.
self.columns = []Column{ func (layout *Layout) Initialize() *Layout {
{ -7, `Ticker`, `Ticker`, nil }, layout.columns = []Column{
{ 10, `LastTrade`, `Last`, currency }, { -7, `Ticker`, `Ticker`, nil },
{ 10, `Change`, `Change`, currency }, { 10, `LastTrade`, `Last`, currency },
{ 10, `ChangePct`, `Change%`, last }, { 10, `Change`, `Change`, currency },
{ 10, `Open`, `Open`, currency }, { 10, `ChangePct`, `Change%`, last },
{ 10, `Low`, `Low`, currency }, { 10, `Open`, `Open`, currency },
{ 10, `High`, `High`, currency }, { 10, `Low`, `Low`, currency },
{ 10, `Low52`, `52w Low`, currency }, { 10, `High`, `High`, currency },
{ 10, `High52`, `52w High`, currency }, { 10, `Low52`, `52w Low`, currency },
{ 11, `Volume`, `Volume`, nil }, { 10, `High52`, `52w High`, currency },
{ 11, `AvgVolume`, `AvgVolume`, nil }, { 11, `Volume`, `Volume`, nil },
{ 9, `PeRatio`, `P/E`, blank }, { 11, `AvgVolume`, `AvgVolume`, nil },
{ 9, `Dividend`, `Dividend`, blank_currency }, { 9, `PeRatio`, `P/E`, blank },
{ 9, `Yield`, `Yield`, percent }, { 9, `Dividend`, `Dividend`, zero },
{ 11, `MarketCap`, `MktCap`, currency }, { 9, `Yield`, `Yield`, percent },
{ 11, `MarketCap`, `MktCap`, currency },
} }
self.regex = regexp.MustCompile(`(\.\d+)[BMK]?$`) layout.regex = regexp.MustCompile(`(\.\d+)[BMK]?$`)
self.market_template = build_market_template() layout.marketTemplate = buildMarketTemplate()
self.quotes_template = build_quotes_template() layout.quotesTemplate = buildQuotesTemplate()
return self return layout
} }
//----------------------------------------------------------------------------- // Market merges given market data structure with the market template and
func (self *Layout) Market(market *Market) string { // returns formatted string that includes highlighting markup.
if ok, err := market.Ok(); !ok { func (layout *Layout) Market(market *Market) string {
return err if ok, err := market.Ok(); !ok { // If there was an error fetching market data...
return err // then simply return the error string.
} }
highlight(market.Dow, market.Sp500, market.Nasdaq) highlight(market.Dow, market.Sp500, market.Nasdaq)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
self.market_template.Execute(buffer, market) layout.marketTemplate.Execute(buffer, market)
return buffer.String() return buffer.String()
} }
//----------------------------------------------------------------------------- // Quotes uses quotes template to format timestamp, stock quotes header,
func (self *Layout) Quotes(quotes *Quotes) string { // and the list of given stock quotes. It returns formatted string with
if ok, err := quotes.Ok(); !ok { // all the necessary markup.
return err func (layout *Layout) Quotes(quotes *Quotes) string {
if ok, err := quotes.Ok(); !ok { // If there was an error fetching stock quotes...
return err // then simply return the error string.
} }
vars := struct { vars := struct {
Now string Now string // Current timestamp.
Header string Header string // Formatted header line.
Stocks []Stock Stocks []Stock // List of formatted stock quotes.
}{ }{
time.Now().Format(`3:04:05pm PST`), time.Now().Format(`3:04:05pm PST`),
self.Header(quotes.profile), layout.Header(quotes.profile),
self.prettify(quotes), layout.prettify(quotes),
} }
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
self.quotes_template.Execute(buffer, vars) layout.quotesTemplate.Execute(buffer, vars)
return buffer.String() return buffer.String()
} }
//----------------------------------------------------------------------------- // Header iterates over column titles and formats the header line. The
func (self *Layout) Header(profile *Profile) string { // formatting includes placing an arrow next to the sorted column title.
str, selected_column := ``, profile.selected_column // When the column editor is active it knows how to highlight currently
// selected column title.
func (layout *Layout) Header(profile *Profile) string {
str, selectedColumn := ``, profile.selectedColumn
for i,col := range self.columns { for i,col := range layout.columns {
arrow := arrow_for(i, profile) arrow := arrowFor(i, profile)
if i != selected_column { if i != selectedColumn {
str += fmt.Sprintf(`%*s`, col.width, arrow + col.title) str += fmt.Sprintf(`%*s`, col.width, arrow + col.title)
} else { } else {
str += fmt.Sprintf(`<r>%*s</r>`, col.width, arrow + col.title) str += fmt.Sprintf(`<r>%*s</r>`, col.width, arrow + col.title)
@ -108,8 +117,14 @@ func (self *Layout) Header(profile *Profile) string {
return `<u>` + str + `</u>` return `<u>` + str + `</u>`
} }
// TotalColumns is the utility method for the column editor that returns
// total number of columns.
func (layout *Layout) TotalColumns() int {
return len(layout.columns)
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Layout) prettify(quotes *Quotes) []Stock { func (layout *Layout) prettify(quotes *Quotes) []Stock {
pretty := make([]Stock, len(quotes.stocks)) pretty := make([]Stock, len(quotes.stocks))
// //
// Iterate over the list of stocks and properly format all its columns. // Iterate over the list of stocks and properly format all its columns.
@ -122,23 +137,23 @@ func (self *Layout) prettify(quotes *Quotes) []Stock {
// - If the column has the formatter method then call it. // - If the column has the formatter method then call it.
// - Set the column value padding it to the given width. // - Set the column value padding it to the given width.
// //
for _,column := range self.columns { for _,column := range layout.columns {
// ex. value = stock.Change // ex. value = stock.Change
value := reflect.ValueOf(&stock).Elem().FieldByName(column.name).String() value := reflect.ValueOf(&stock).Elem().FieldByName(column.name).String()
if column.formatter != nil { if column.formatter != nil {
// ex. value = currency(value) // ex. value = currency(value)
value = column.formatter(value) value = column.formatter(value)
} }
// ex. pretty[i].Change = self.pad(value, 10) // ex. pretty[i].Change = layout.pad(value, 10)
reflect.ValueOf(&pretty[i]).Elem().FieldByName(column.name).SetString(self.pad(value, column.width)) reflect.ValueOf(&pretty[i]).Elem().FieldByName(column.name).SetString(layout.pad(value, column.width))
} }
} }
profile := quotes.profile profile := quotes.profile
if self.sorter == nil { // Initialize sorter on first invocation. if layout.sorter == nil { // Initialize sorter on first invocation.
self.sorter = new(Sorter).Initialize(profile) layout.sorter = new(Sorter).Initialize(profile)
} }
self.sorter.SortByCurrentColumn(pretty) layout.sorter.SortByCurrentColumn(pretty)
// //
// Group stocks by advancing/declining unless sorted by Chanage or Change% // Group stocks by advancing/declining unless sorted by Chanage or Change%
// in which case the grouping has been done already. // in which case the grouping has been done already.
@ -151,8 +166,8 @@ func (self *Layout) prettify(quotes *Quotes) []Stock {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Layout) pad(str string, width int) string { func (layout *Layout) pad(str string, width int) string {
match := self.regex.FindStringSubmatch(str) match := layout.regex.FindStringSubmatch(str)
if len(match) > 0 { if len(match) > 0 {
switch len(match[1]) { switch len(match[1]) {
case 2: case 2:
@ -166,7 +181,7 @@ func (self *Layout) pad(str string, width int) string {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func build_market_template() *template.Template { func buildMarketTemplate() *template.Template {
markup := `{{.Dow.name}}: {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}}, {{.Sp500.name}}: {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}}, {{.Nasdaq.name}}: {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}} markup := `{{.Dow.name}}: {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}}, {{.Sp500.name}}: {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}}, {{.Nasdaq.name}}: {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}}
{{.Advances.name}}: {{.Advances.nyse}} ({{.Advances.nysep}}) on NYSE and {{.Advances.nasdaq}} ({{.Advances.nasdaqp}}) on Nasdaq. {{.Declines.name}}: {{.Declines.nyse}} ({{.Declines.nysep}}) on NYSE and {{.Declines.nasdaq}} ({{.Declines.nasdaqp}}) on Nasdaq {{if .IsClosed}}<right>U.S. markets closed</right>{{end}} {{.Advances.name}}: {{.Advances.nyse}} ({{.Advances.nysep}}) on NYSE and {{.Advances.nasdaq}} ({{.Advances.nasdaqp}}) on Nasdaq. {{.Declines.name}}: {{.Declines.nyse}} ({{.Declines.nysep}}) on NYSE and {{.Declines.nasdaq}} ({{.Declines.nasdaqp}}) on Nasdaq {{if .IsClosed}}<right>U.S. markets closed</right>{{end}}
New highs: {{.Highs.nyse}} on NYSE and {{.Highs.nasdaq}} on Nasdaq. New lows: {{.Lows.nyse}} on NYSE and {{.Lows.nasdaq}} on Nasdaq.` New highs: {{.Highs.nyse}} on NYSE and {{.Highs.nasdaq}} on Nasdaq. New lows: {{.Lows.nyse}} on NYSE and {{.Lows.nasdaq}} on Nasdaq.`
@ -175,7 +190,7 @@ New highs: {{.Highs.nyse}} on NYSE and {{.Highs.nasdaq}} on Nasdaq. New lows: {{
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func build_quotes_template() *template.Template { func buildQuotesTemplate() *template.Template {
markup := `<right><white>{{.Now}}</></right> markup := `<right><white>{{.Now}}</></right>
@ -218,7 +233,7 @@ func group(stocks []Stock) []Stock {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func arrow_for(column int, profile *Profile) string { func arrowFor(column int, profile *Profile) string {
if column == profile.SortColumn { if column == profile.SortColumn {
if profile.Ascending { if profile.Ascending {
return string('\U00002191') return string('\U00002191')
@ -238,7 +253,7 @@ func blank(str string) string {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func blank_currency(str string) string { func zero(str string) string {
if str == `0.00` { if str == `0.00` {
return `-` return `-`
} }

@ -21,7 +21,7 @@ type Profile struct {
Tickers []string Tickers []string
SortColumn int SortColumn int
Ascending bool Ascending bool
selected_column int selectedColumn int
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -39,7 +39,7 @@ func (self *Profile) Initialize() *Profile {
} else { } else {
json.Unmarshal(data, self) json.Unmarshal(data, self)
} }
self.selected_column = -1 self.selectedColumn = -1
return self return self
} }
@ -95,10 +95,10 @@ func (self *Profile) RemoveTickers(tickers []string) (removed int, err error) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Profile) Reorder() error { func (self *Profile) Reorder() error {
if self.selected_column == self.SortColumn { if self.selectedColumn == self.SortColumn {
self.Ascending = !self.Ascending self.Ascending = !self.Ascending
} else { } else {
self.SortColumn = self.selected_column self.SortColumn = self.selectedColumn
} }
return self.Save() return self.Save()
} }

Loading…
Cancel
Save