diff --git a/LICENSE b/LICENSE index 6e65a05..9437929 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013 Michael Dvorkin. All Rights Reserved. +Copyright (c) 2013-2015 Michael Dvorkin. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 156800f..965ca44 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ comments, suggestions, and contributions are welcome. ### License ### -Copyright (c) 2013 Michael Dvorkin. All Rights Reserved. +Copyright (c) 2013-2015 Michael Dvorkin. All Rights Reserved. "mike" + "@dvorkin" + ".net" || "twitter.com/mid" Permission is hereby granted, free of charge, to any person obtaining diff --git a/cmd/mop.go b/cmd/mop.go index 53442df..52a96dc 100644 --- a/cmd/mop.go +++ b/cmd/mop.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -10,7 +10,7 @@ import ( `time` ) -const help = `Mop v0.2.0 -- Copyright (c) 2013-15 Michael Dvorkin. All Rights Reserved. +const help = `Mop v0.2.0 -- Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. NO WARRANTIES OF ANY KIND WHATSOEVER. SEE THE LICENSE FILE FOR DETAILS. Command Description diff --git a/cnn_market.go b/cnn_market.go index 126e800..d8fb47b 100644 --- a/cnn_market.go +++ b/cnn_market.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -18,61 +18,61 @@ const marketURL = `http://money.cnn.com/data/markets/` // Market stores current market information displayed in the top three lines of // the screen. The market data is fetched and parsed from the HTML page above. type Market struct { - IsClosed bool // True when U.S. markets are closed. - Dow map[string]string // Hash of Dow Jones indicators. - Nasdaq map[string]string // Hash of NASDAQ indicators. - Sp500 map[string]string // Hash of S&P 500 indicators. - Tokyo map[string]string - HongKong map[string]string - London map[string]string - Frankfurt map[string]string - Yield map[string]string - Oil map[string]string - Yen map[string]string - Euro map[string]string - Gold map[string]string - regex *regexp.Regexp // Regex to parse market data from HTML. - errors string // Error(s), if any. + IsClosed bool // True when U.S. markets are closed. + Dow map[string]string // Hash of Dow Jones indicators. + Nasdaq map[string]string // Hash of NASDAQ indicators. + Sp500 map[string]string // Hash of S&P 500 indicators. + Tokyo map[string]string + HongKong map[string]string + London map[string]string + Frankfurt map[string]string + Yield map[string]string + Oil map[string]string + Yen map[string]string + Euro map[string]string + Gold map[string]string + regex *regexp.Regexp // Regex to parse market data from HTML. + errors string // Error(s), if any. } // Returns new initialized Market struct. func NewMarket() *Market { - market := &Market{}; - market.IsClosed = false - market.Dow = make(map[string]string) - market.Nasdaq = make(map[string]string) - market.Sp500 = make(map[string]string) - - market.Tokyo = make(map[string]string) - market.HongKong = make(map[string]string) - market.London = make(map[string]string) + market := &Market{} + market.IsClosed = false + market.Dow = make(map[string]string) + market.Nasdaq = make(map[string]string) + market.Sp500 = make(map[string]string) + + market.Tokyo = make(map[string]string) + market.HongKong = make(map[string]string) + market.London = make(map[string]string) market.Frankfurt = make(map[string]string) - market.Yield = make(map[string]string) - market.Oil = make(map[string]string) - market.Yen = make(map[string]string) - market.Euro = make(map[string]string) - market.Gold = make(map[string]string) + market.Yield = make(map[string]string) + market.Oil = make(map[string]string) + market.Yen = make(map[string]string) + market.Euro = make(map[string]string) + market.Gold = make(map[string]string) - market.errors = `` + market.errors = `` const any = `\s*(?:.+?)` const price = `>([\d\.,]+)` const percent = `>([\+\-]?[\d\.,]+%?)<` rules := []string{ - `>Dow<`, any, percent, any, price, any, percent, any, - `>Nasdaq<`, any, percent, any, price, any, percent, any, - `">S&P<`, any, percent, any, price, any, percent, any, + `>Dow<`, any, percent, any, price, any, percent, any, + `>Nasdaq<`, any, percent, any, price, any, percent, any, + `">S&P<`, any, percent, any, price, any, percent, any, `>Nikkei 225<`, any, percent, any, price, any, percent, any, - `>Hang Seng<`, any, percent, any, price, any, percent, any, - `>FTSE 100<`, any, percent, any, price, any, percent, any, - `>DAX<`, any, percent, any, price, any, percent, any, - `>10-year yield<`, any, price, any, percent, any, - `>Oil<`, any, price, any, percent, any, - `>Yen<`, any, price, any, percent, any, - `>Euro<`, any, price, any, percent, any, - `>Gold<`, any, price, any, percent, any, + `>Hang Seng<`, any, percent, any, price, any, percent, any, + `>FTSE 100<`, any, percent, any, price, any, percent, any, + `>DAX<`, any, percent, any, price, any, percent, any, + `>10-year yield<`, any, price, any, percent, any, + `>Oil<`, any, price, any, percent, any, + `>Yen<`, any, price, any, percent, any, + `>Euro<`, any, price, any, percent, any, + `>Gold<`, any, price, any, percent, any, } market.regex = regexp.MustCompile(strings.Join(rules, ``)) @@ -131,9 +131,9 @@ func (market *Market) trim(body []byte) []byte { func (market *Market) extract(snippet []byte) *Market { matches := market.regex.FindStringSubmatch(string(snippet)) - if len(matches) < 31 { - panic(`Unable to parse ` + marketURL) - } + if len(matches) < 31 { + panic(`Unable to parse ` + marketURL) + } market.Dow[`change`] = matches[1] market.Dow[`latest`] = matches[2] diff --git a/column_editor.go b/column_editor.go index 699bcde..b008d5f 100644 --- a/column_editor.go +++ b/column_editor.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -10,19 +10,19 @@ import `github.com/michaeldv/termbox-go` // current column name in the header, then waits for arrow keys (choose // another column), Enter (reverse sort order), or Esc (exit). type ColumnEditor struct { - screen *Screen // Pointer to Screen so we could use screen.Draw(). - quotes *Quotes // Pointer to Quotes to redraw them when the sort order changes. - layout *Layout // Pointer to Layout to redraw stock quotes header. - profile *Profile // Pointer to Profile where we save newly selected sort order. + screen *Screen // Pointer to Screen so we could use screen.Draw(). + quotes *Quotes // Pointer to Quotes to redraw them when the sort order changes. + layout *Layout // Pointer to Layout to redraw stock quotes header. + profile *Profile // Pointer to Profile where we save newly selected sort order. } // Returns new initialized ColumnEditor struct. As part of initialization it // highlights current column name (as stored in Profile). func NewColumnEditor(screen *Screen, quotes *Quotes) *ColumnEditor { editor := &ColumnEditor{ - screen: screen, - quotes: quotes, - layout: screen.layout, + screen: screen, + quotes: quotes, + layout: screen.layout, profile: quotes.profile, } @@ -43,7 +43,7 @@ func (editor *ColumnEditor) Handle(event termbox.Event) bool { case termbox.KeyEnter: editor.execute() - case termbox.KeyArrowLeft: + case termbox.KeyArrowLeft: editor.selectLeftColumn() case termbox.KeyArrowRight: @@ -72,7 +72,7 @@ func (editor *ColumnEditor) selectLeftColumn() *ColumnEditor { //----------------------------------------------------------------------------- func (editor *ColumnEditor) selectRightColumn() *ColumnEditor { editor.profile.selectedColumn++ - if editor.profile.selectedColumn > editor.layout.TotalColumns() - 1 { + if editor.profile.selectedColumn > editor.layout.TotalColumns()-1 { editor.profile.selectedColumn = 0 } return editor @@ -98,4 +98,3 @@ func (editor *ColumnEditor) redrawHeader() { editor.screen.DrawLine(0, 4, editor.layout.Header(editor.profile)) termbox.Flush() } - diff --git a/layout.go b/layout.go index d6ff386..156b73a 100644 --- a/layout.go +++ b/layout.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -17,41 +17,41 @@ import ( // Column describes formatting rules for individual column within the list // of stock quotes. type Column struct { - width int // Column width. - name string // The name of the field in the Stock struct. - title string // Column title to display in the header. - formatter func(string)string // Optional function to format the contents of the column. + width int // Column width. + name string // The name of the field in the Stock struct. + title string // Column title to display in the header. + 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 { - columns []Column // List of stock quotes columns. - sorter *Sorter // Pointer to sorting receiver. - regex *regexp.Regexp // Pointer to regular expression to align decimal points. - marketTemplate *template.Template // Pointer to template to format market data. - quotesTemplate *template.Template // Pointer to template to format the list of stock quotes. + columns []Column // List of stock quotes columns. + sorter *Sorter // Pointer to sorting receiver. + regex *regexp.Regexp // Pointer to regular expression to align decimal points. + marketTemplate *template.Template // Pointer to template to format market data. + quotesTemplate *template.Template // Pointer to template to format the list of stock quotes. } // Creates the layout and assigns the default values that stay unchanged. func NewLayout() *Layout { layout := &Layout{} layout.columns = []Column{ - { -7, `Ticker`, `Ticker`, nil }, - { 10, `LastTrade`, `Last`, currency }, - { 10, `Change`, `Change`, currency }, - { 10, `ChangePct`, `Change%`, last }, - { 10, `Open`, `Open`, currency }, - { 10, `Low`, `Low`, currency }, - { 10, `High`, `High`, currency }, - { 10, `Low52`, `52w Low`, currency }, - { 10, `High52`, `52w High`, currency }, - { 11, `Volume`, `Volume`, nil }, - { 11, `AvgVolume`, `AvgVolume`, nil }, - { 9, `PeRatio`, `P/E`, blank }, - { 9, `Dividend`, `Dividend`, zero }, - { 9, `Yield`, `Yield`, percent }, - { 11, `MarketCap`, `MktCap`, currency }, + {-7, `Ticker`, `Ticker`, nil}, + {10, `LastTrade`, `Last`, currency}, + {10, `Change`, `Change`, currency}, + {10, `ChangePct`, `Change%`, last}, + {10, `Open`, `Open`, currency}, + {10, `Low`, `Low`, currency}, + {10, `High`, `High`, currency}, + {10, `Low52`, `52w Low`, currency}, + {10, `High52`, `52w High`, currency}, + {11, `Volume`, `Volume`, nil}, + {11, `AvgVolume`, `AvgVolume`, nil}, + {9, `PeRatio`, `P/E`, blank}, + {9, `Dividend`, `Dividend`, zero}, + {9, `Yield`, `Yield`, percent}, + {11, `MarketCap`, `MktCap`, currency}, } layout.regex = regexp.MustCompile(`(\.\d+)[BMK]?$`) layout.marketTemplate = buildMarketTemplate() @@ -63,8 +63,8 @@ func NewLayout() *Layout { // Market merges given market data structure with the market template and // returns formatted string that includes highlighting markup. func (layout *Layout) Market(market *Market) string { - if ok, err := market.Ok(); !ok { // If there was an error fetching market data... - return err // then simply return the error string. + 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, @@ -80,14 +80,14 @@ func (layout *Layout) Market(market *Market) string { // and the list of given stock quotes. It returns formatted string with // all the necessary markup. 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. + if ok, err := quotes.Ok(); !ok { // If there was an error fetching stock quotes... + return err // then simply return the error string. } vars := struct { - Now string // Current timestamp. - Header string // Formatted header line. - Stocks []Stock // List of formatted stock quotes. + Now string // Current timestamp. + Header string // Formatted header line. + Stocks []Stock // List of formatted stock quotes. }{ time.Now().Format(`3:04:05pm PST`), layout.Header(quotes.profile), @@ -107,12 +107,12 @@ func (layout *Layout) Quotes(quotes *Quotes) string { func (layout *Layout) Header(profile *Profile) string { str, selectedColumn := ``, profile.selectedColumn - for i,col := range layout.columns { + for i, col := range layout.columns { arrow := arrowFor(i, profile) if i != selectedColumn { - str += fmt.Sprintf(`%*s`, col.width, arrow + col.title) + str += fmt.Sprintf(`%*s`, col.width, arrow+col.title) } else { - str += fmt.Sprintf(`%*s`, col.width, arrow + col.title) + str += fmt.Sprintf(`%*s`, col.width, arrow+col.title) } } @@ -139,7 +139,7 @@ func (layout *Layout) prettify(quotes *Quotes) []Stock { // - If the column has the formatter method then call it. // - Set the column value padding it to the given width. // - for _,column := range layout.columns { + for _, column := range layout.columns { // ex. value = stock.Change value := reflect.ValueOf(&stock).Elem().FieldByName(column.name).String() if column.formatter != nil { @@ -173,7 +173,7 @@ func (layout *Layout) pad(str string, width int) string { if len(match) > 0 { switch len(match[1]) { case 2: - str = strings.Replace(str, match[1], match[1] + `0`, 1) + str = strings.Replace(str, match[1], match[1]+`0`, 1) case 4, 5: str = strings.Replace(str, match[1], match[1][0:3], 1) } @@ -218,13 +218,13 @@ func group(stocks []Stock) []Stock { grouped := make([]Stock, len(stocks)) current := 0 - for _,stock := range stocks { + for _, stock := range stocks { if stock.Advancing { grouped[current] = stock current++ } } - for _,stock := range stocks { + for _, stock := range stocks { if !stock.Advancing { grouped[current] = stock current++ diff --git a/line_editor.go b/line_editor.go index 03cab55..a5b6201 100644 --- a/line_editor.go +++ b/line_editor.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -15,13 +15,13 @@ import ( // data and keep track of cursor movements (left, right, beginning of the // line, end of the line, and backspace). type LineEditor struct { - command rune // Keyboard command such as '+' or '-'. - cursor int // Current cursor position within the input line. - prompt string // Prompt string for the command. - input string // User typed input string. - screen *Screen // Pointer to Screen. - quotes *Quotes // Pointer to Quotes. - regex *regexp.Regexp // Regex to split comma-delimited input string. + command rune // Keyboard command such as '+' or '-'. + cursor int // Current cursor position within the input line. + prompt string // Prompt string for the command. + input string // User typed input string. + screen *Screen // Pointer to Screen. + quotes *Quotes // Pointer to Quotes. + regex *regexp.Regexp // Regex to split comma-delimited input string. } // Returns new initialized LineEditor struct. @@ -29,7 +29,7 @@ func NewLineEditor(screen *Screen, quotes *Quotes) *LineEditor { return &LineEditor{ screen: screen, quotes: quotes, - regex: regexp.MustCompile(`[,\s]+`), + regex: regexp.MustCompile(`[,\s]+`), } } @@ -42,7 +42,7 @@ func (editor *LineEditor) Prompt(command rune) *LineEditor { editor.prompt = prompt editor.command = command - editor.screen.DrawLine(0, 3, `` + editor.prompt + ``) + editor.screen.DrawLine(0, 3, ``+editor.prompt+``) termbox.SetCursor(len(editor.prompt), 3) termbox.Flush() } @@ -65,7 +65,7 @@ func (editor *LineEditor) Handle(ev termbox.Event) bool { case termbox.KeyEnter: return editor.execute().done() - case termbox.KeyBackspace, termbox.KeyBackspace2: + case termbox.KeyBackspace, termbox.KeyBackspace2: editor.deletePreviousCharacter() case termbox.KeyCtrlB, termbox.KeyArrowLeft: @@ -97,12 +97,12 @@ func (editor *LineEditor) deletePreviousCharacter() *LineEditor { if editor.cursor > 0 { if editor.cursor < len(editor.input) { // Remove character in the middle of the input string. - editor.input = editor.input[0 : editor.cursor-1] + editor.input[editor.cursor : len(editor.input)] + editor.input = editor.input[0:editor.cursor-1] + editor.input[editor.cursor:len(editor.input)] } else { // Remove last input character. - editor.input = editor.input[ : len(editor.input)-1] + editor.input = editor.input[:len(editor.input)-1] } - editor.screen.DrawLine(len(editor.prompt), 3, editor.input + ` `) // Erase last character. + editor.screen.DrawLine(len(editor.prompt), 3, editor.input+` `) // Erase last character. editor.moveLeft() } @@ -113,7 +113,7 @@ func (editor *LineEditor) deletePreviousCharacter() *LineEditor { func (editor *LineEditor) insertCharacter(ch rune) *LineEditor { if editor.cursor < len(editor.input) { // Insert the character in the middle of the input string. - editor.input = editor.input[0 : editor.cursor] + string(ch) + editor.input[editor.cursor : len(editor.input)] + editor.input = editor.input[0:editor.cursor] + string(ch) + editor.input[editor.cursor:len(editor.input)] } else { // Append the character to the end of the input string. editor.input += string(ch) @@ -128,7 +128,7 @@ func (editor *LineEditor) insertCharacter(ch rune) *LineEditor { func (editor *LineEditor) moveLeft() *LineEditor { if editor.cursor > 0 { editor.cursor-- - termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) + termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) } return editor @@ -138,7 +138,7 @@ func (editor *LineEditor) moveLeft() *LineEditor { func (editor *LineEditor) moveRight() *LineEditor { if editor.cursor < len(editor.input) { editor.cursor++ - termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) + termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) } return editor @@ -147,7 +147,7 @@ func (editor *LineEditor) moveRight() *LineEditor { //----------------------------------------------------------------------------- func (editor *LineEditor) jumpToBeginning() *LineEditor { editor.cursor = 0 - termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) + termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) return editor } @@ -155,7 +155,7 @@ func (editor *LineEditor) jumpToBeginning() *LineEditor { //----------------------------------------------------------------------------- func (editor *LineEditor) jumpToEnd() *LineEditor { editor.cursor = len(editor.input) - termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) + termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) return editor } @@ -166,7 +166,7 @@ func (editor *LineEditor) execute() *LineEditor { case '+': tickers := editor.tokenize() if len(tickers) > 0 { - if added,_ := editor.quotes.AddTickers(tickers); added > 0 { + if added, _ := editor.quotes.AddTickers(tickers); added > 0 { editor.screen.Draw(editor.quotes) } } @@ -174,13 +174,13 @@ func (editor *LineEditor) execute() *LineEditor { tickers := editor.tokenize() if len(tickers) > 0 { before := len(editor.quotes.profile.Tickers) - if removed,_ := editor.quotes.RemoveTickers(tickers); removed > 0 { + if removed, _ := editor.quotes.RemoveTickers(tickers); removed > 0 { editor.screen.Draw(editor.quotes) // Clear the lines at the bottom of the list, if any. after := before - removed for i := before; i > after; i-- { - editor.screen.ClearLine(0, i + 4) + editor.screen.ClearLine(0, i+4) } } } diff --git a/markup.go b/markup.go index fae6b1e..bffca94 100644 --- a/markup.go +++ b/markup.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -24,36 +24,36 @@ import ( // The ... tag is used to right align the enclosed string // (ex. when displaying current time in the upper right corner). type Markup struct { - Foreground termbox.Attribute // Foreground color. - Background termbox.Attribute // Background color (so far always termbox.ColorDefault). - RightAligned bool // True when the string is right aligned. - tags map[string]termbox.Attribute // Tags to Termbox translation hash. - regex *regexp.Regexp // Regex to identify the supported tag names. + Foreground termbox.Attribute // Foreground color. + Background termbox.Attribute // Background color (so far always termbox.ColorDefault). + RightAligned bool // True when the string is right aligned. + tags map[string]termbox.Attribute // Tags to Termbox translation hash. + regex *regexp.Regexp // Regex to identify the supported tag names. } // Creates markup to define tag to Termbox translation rules and store default // colors and column alignments. func NewMarkup() *Markup { markup := &Markup{} - markup.Foreground = termbox.ColorDefault - markup.Background = termbox.ColorDefault - markup.RightAligned = false + markup.Foreground = termbox.ColorDefault + markup.Background = termbox.ColorDefault + markup.RightAligned = false markup.tags = make(map[string]termbox.Attribute) - markup.tags[`/`] = termbox.ColorDefault - markup.tags[`black`] = termbox.ColorBlack - markup.tags[`red`] = termbox.ColorRed - markup.tags[`green`] = termbox.ColorGreen - markup.tags[`yellow`] = termbox.ColorYellow - markup.tags[`blue`] = termbox.ColorBlue + markup.tags[`/`] = termbox.ColorDefault + markup.tags[`black`] = termbox.ColorBlack + markup.tags[`red`] = termbox.ColorRed + markup.tags[`green`] = termbox.ColorGreen + markup.tags[`yellow`] = termbox.ColorYellow + markup.tags[`blue`] = termbox.ColorBlue markup.tags[`magenta`] = termbox.ColorMagenta - markup.tags[`cyan`] = termbox.ColorCyan - markup.tags[`white`] = termbox.ColorWhite - markup.tags[`right`] = termbox.ColorDefault // Termbox can combine attributes and a single color using bitwise OR. - markup.tags[`b`] = termbox.AttrBold // Attribute = 1 << (iota + 4) - markup.tags[`u`] = termbox.AttrUnderline - markup.tags[`r`] = termbox.AttrReverse - markup.regex = markup.supportedTags() // Once we have the hash we could build the regex. + markup.tags[`cyan`] = termbox.ColorCyan + markup.tags[`white`] = termbox.ColorWhite + markup.tags[`right`] = termbox.ColorDefault // Termbox can combine attributes and a single color using bitwise OR. + markup.tags[`b`] = termbox.AttrBold // Attribute = 1 << (iota + 4) + markup.tags[`u`] = termbox.AttrUnderline + markup.tags[`r`] = termbox.AttrReverse + markup.regex = markup.supportedTags() // Once we have the hash we could build the regex. return markup } @@ -111,17 +111,17 @@ func (markup *Markup) process(tag string, open bool) bool { if attribute, ok := markup.tags[tag]; ok { switch tag { case `right`: - markup.RightAligned = open // On for , off for . + markup.RightAligned = open // On for , off for . default: if open { if attribute >= termbox.AttrBold { - markup.Foreground |= attribute // Set the Termbox attribute. + markup.Foreground |= attribute // Set the Termbox attribute. } else { - markup.Foreground = attribute // Set the Termbox color. + markup.Foreground = attribute // Set the Termbox color. } } else { if attribute >= termbox.AttrBold { - markup.Foreground &= ^attribute // Clear the Termbox attribute. + markup.Foreground &= ^attribute // Clear the Termbox attribute. } else { markup.Foreground = termbox.ColorDefault } @@ -138,7 +138,7 @@ func (markup *Markup) supportedTags() *regexp.Regexp { arr := []string{} for tag := range markup.tags { - arr = append(arr, ``) + arr = append(arr, ``) } return regexp.MustCompile(strings.Join(arr, `|`)) diff --git a/profile.go b/profile.go index e86b3cd..ce70d38 100644 --- a/profile.go +++ b/profile.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -18,13 +18,13 @@ const moprc = `/.moprc` // stock tickers). The settings are serialized using JSON and saved in // the ~/.moprc file. type Profile struct { - Tickers []string // List of stock tickers to display. - MarketRefresh int // Time interval to refresh market data. - QuotesRefresh int // Time interval to refresh stock quotes. - SortColumn int // Column number by which we sort stock quotes. - Ascending bool // True when sort order is ascending. - Grouped bool // True when stocks are grouped by advancing/declining. - selectedColumn int // Stores selected column number when the column editor is active. + Tickers []string // List of stock tickers to display. + MarketRefresh int // Time interval to refresh market data. + QuotesRefresh int // Time interval to refresh stock quotes. + SortColumn int // Column number by which we sort stock quotes. + Ascending bool // True when sort order is ascending. + Grouped bool // True when stocks are grouped by advancing/declining. + selectedColumn int // Stores selected column number when the column editor is active. } // Creates the profile and attempts to load the settings from ~/.moprc file. @@ -32,13 +32,13 @@ type Profile struct { func NewProfile() *Profile { profile := &Profile{} data, err := ioutil.ReadFile(profile.defaultFileName()) - if err != nil { // Set default values: - profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute). - profile.QuotesRefresh = 5 // Stock quotes get updated every 5s (12 times per minute). - profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining. - profile.Tickers = []string{ `AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V` } - profile.SortColumn = 0 // Stock quotes are sorted by ticker name. - profile.Ascending = true // A to Z. + if err != nil { // Set default values: + profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute). + profile.QuotesRefresh = 5 // Stock quotes get updated every 5s (12 times per minute). + profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining. + profile.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} + profile.SortColumn = 0 // Stock quotes are sorted by ticker name. + profile.Ascending = true // A to Z. profile.Save() } else { json.Unmarshal(data, profile) @@ -110,9 +110,9 @@ func (profile *Profile) RemoveTickers(tickers []string) (removed int, err error) // for the current column, or to pick another sort column. func (profile *Profile) Reorder() error { if profile.selectedColumn == profile.SortColumn { - profile.Ascending = !profile.Ascending // Reverse sort order. + profile.Ascending = !profile.Ascending // Reverse sort order. } else { - profile.SortColumn = profile.selectedColumn // Pick new sort column. + profile.SortColumn = profile.selectedColumn // Pick new sort column. } return profile.Save() } diff --git a/screen.go b/screen.go index 8774410..b1c4e87 100644 --- a/screen.go +++ b/screen.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -13,12 +13,12 @@ import ( // Screen is thin wrapper aroung Termbox library to provide basic display // capabilities as requied by Mop. type Screen struct { - width int // Current number of columns. - height int // Current number of rows. - cleared bool // True after the screens gets cleared. - layout *Layout // Pointer to layout (gets created by screen). - markup *Markup // Pointer to markup processor (gets created by screen). - pausedAt *time.Time // Timestamp of the pause request or nil if none. + width int // Current number of columns. + height int // Current number of rows. + cleared bool // True after the screens gets cleared. + layout *Layout // Pointer to layout (gets created by screen). + markup *Markup // Pointer to markup processor (gets created by screen). + pausedAt *time.Time // Timestamp of the pause request or nil if none. } // Initializes Termbox, creates screen along with layout and markup, and @@ -54,14 +54,14 @@ func (screen *Screen) Resize() *Screen { // Pause is a toggle function that either creates a timestamp of the pause // request or resets it to nil. func (screen *Screen) Pause(pause bool) *Screen { - if pause { - screen.pausedAt = new(time.Time) - *screen.pausedAt = time.Now() - } else { - screen.pausedAt = nil - } - - return screen + if pause { + screen.pausedAt = new(time.Time) + *screen.pausedAt = time.Now() + } else { + screen.pausedAt = nil + } + + return screen } // Clear makes the entire screen blank using default background color. @@ -86,9 +86,9 @@ func (screen *Screen) ClearLine(x int, y int) *Screen { // Draw accepts variable number of arguments and knows how to display the // market data, stock quotes, current time, and an arbitrary string. func (screen *Screen) Draw(objects ...interface{}) *Screen { - if screen.pausedAt != nil { - defer screen.DrawLine(0, 0, `` + screen.pausedAt.Format(`3:04:05pm PST`) + ``) - } + if screen.pausedAt != nil { + defer screen.DrawLine(0, 0, ``+screen.pausedAt.Format(`3:04:05pm PST`)+``) + } for _, ptr := range objects { switch ptr.(type) { case *Market: @@ -99,7 +99,7 @@ func (screen *Screen) Draw(objects ...interface{}) *Screen { screen.draw(screen.layout.Quotes(object.Fetch())) case time.Time: timestamp := ptr.(time.Time).Format(`3:04:05pm PST`) - screen.DrawLine(0, 0, `` + timestamp + ``) + screen.DrawLine(0, 0, ``+timestamp+``) default: screen.draw(ptr.(string)) } diff --git a/sorter.go b/sorter.go index f02e178..576d98f 100644 --- a/sorter.go +++ b/sorter.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -14,76 +14,133 @@ import ( // setup is rather lengthy; there should probably be more concise way // that uses reflection and avoids hardcoding the column names. type Sorter struct { - profile *Profile // Pointer to where we store sort column and order. + profile *Profile // Pointer to where we store sort column and order. } type sortable []Stock -func (list sortable) Len() int { return len(list) } + +func (list sortable) Len() int { return len(list) } func (list sortable) Swap(i, j int) { list[i], list[j] = list[j], list[i] } -type byTickerAsc struct { sortable } -type byLastTradeAsc struct { sortable } -type byChangeAsc struct { sortable } -type byChangePctAsc struct { sortable } -type byOpenAsc struct { sortable } -type byLowAsc struct { sortable } -type byHighAsc struct { sortable } -type byLow52Asc struct { sortable } -type byHigh52Asc struct { sortable } -type byVolumeAsc struct { sortable } -type byAvgVolumeAsc struct { sortable } -type byPeRatioAsc struct { sortable } -type byDividendAsc struct { sortable } -type byYieldAsc struct { sortable } -type byMarketCapAsc struct { sortable } - -type byTickerDesc struct { sortable } -type byLastTradeDesc struct { sortable } -type byChangeDesc struct { sortable } -type byChangePctDesc struct { sortable } -type byOpenDesc struct { sortable } -type byLowDesc struct { sortable } -type byHighDesc struct { sortable } -type byLow52Desc struct { sortable } -type byHigh52Desc struct { sortable } -type byVolumeDesc struct { sortable } -type byAvgVolumeDesc struct { sortable } -type byPeRatioDesc struct { sortable } -type byDividendDesc struct { sortable } -type byYieldDesc struct { sortable } -type byMarketCapDesc struct { sortable } - -func (list byTickerAsc) Less(i, j int) bool { return list.sortable[i].Ticker < list.sortable[j].Ticker } -func (list byLastTradeAsc) Less(i, j int) bool { return list.sortable[i].LastTrade < list.sortable[j].LastTrade } -func (list byChangeAsc) Less(i, j int) bool { return c(list.sortable[i].Change) < c(list.sortable[j].Change) } -func (list byChangePctAsc) Less(i, j int) bool { return c(list.sortable[i].ChangePct) < c(list.sortable[j].ChangePct) } -func (list byOpenAsc) Less(i, j int) bool { return list.sortable[i].Open < list.sortable[j].Open } -func (list byLowAsc) Less(i, j int) bool { return list.sortable[i].Low < list.sortable[j].Low } -func (list byHighAsc) Less(i, j int) bool { return list.sortable[i].High < list.sortable[j].High } -func (list byLow52Asc) Less(i, j int) bool { return list.sortable[i].Low52 < list.sortable[j].Low52 } -func (list byHigh52Asc) Less(i, j int) bool { return list.sortable[i].High52 < list.sortable[j].High52 } -func (list byVolumeAsc) Less(i, j int) bool { return list.sortable[i].Volume < list.sortable[j].Volume } -func (list byAvgVolumeAsc) Less(i, j int) bool { return list.sortable[i].AvgVolume < list.sortable[j].AvgVolume } -func (list byPeRatioAsc) Less(i, j int) bool { return list.sortable[i].PeRatio < list.sortable[j].PeRatio } -func (list byDividendAsc) Less(i, j int) bool { return list.sortable[i].Dividend < list.sortable[j].Dividend } -func (list byYieldAsc) Less(i, j int) bool { return list.sortable[i].Yield < list.sortable[j].Yield } -func (list byMarketCapAsc) Less(i, j int) bool { return m(list.sortable[i].MarketCap) < m(list.sortable[j].MarketCap) } - -func (list byTickerDesc) Less(i, j int) bool { return list.sortable[j].Ticker < list.sortable[i].Ticker } -func (list byLastTradeDesc) Less(i, j int) bool { return list.sortable[j].LastTrade < list.sortable[i].LastTrade } -func (list byChangeDesc) Less(i, j int) bool { return c(list.sortable[j].ChangePct) < c(list.sortable[i].ChangePct) } -func (list byChangePctDesc) Less(i, j int) bool { return c(list.sortable[j].ChangePct) < c(list.sortable[i].ChangePct) } -func (list byOpenDesc) Less(i, j int) bool { return list.sortable[j].Open < list.sortable[i].Open } -func (list byLowDesc) Less(i, j int) bool { return list.sortable[j].Low < list.sortable[i].Low } -func (list byHighDesc) Less(i, j int) bool { return list.sortable[j].High < list.sortable[i].High } -func (list byLow52Desc) Less(i, j int) bool { return list.sortable[j].Low52 < list.sortable[i].Low52 } -func (list byHigh52Desc) Less(i, j int) bool { return list.sortable[j].High52 < list.sortable[i].High52 } -func (list byVolumeDesc) Less(i, j int) bool { return list.sortable[j].Volume < list.sortable[i].Volume } -func (list byAvgVolumeDesc) Less(i, j int) bool { return list.sortable[j].AvgVolume < list.sortable[i].AvgVolume } -func (list byPeRatioDesc) Less(i, j int) bool { return list.sortable[j].PeRatio < list.sortable[i].PeRatio } -func (list byDividendDesc) Less(i, j int) bool { return list.sortable[j].Dividend < list.sortable[i].Dividend } -func (list byYieldDesc) Less(i, j int) bool { return list.sortable[j].Yield < list.sortable[i].Yield } -func (list byMarketCapDesc) Less(i, j int) bool { return m(list.sortable[j].MarketCap) < m(list.sortable[i].MarketCap) } +type byTickerAsc struct{ sortable } +type byLastTradeAsc struct{ sortable } +type byChangeAsc struct{ sortable } +type byChangePctAsc struct{ sortable } +type byOpenAsc struct{ sortable } +type byLowAsc struct{ sortable } +type byHighAsc struct{ sortable } +type byLow52Asc struct{ sortable } +type byHigh52Asc struct{ sortable } +type byVolumeAsc struct{ sortable } +type byAvgVolumeAsc struct{ sortable } +type byPeRatioAsc struct{ sortable } +type byDividendAsc struct{ sortable } +type byYieldAsc struct{ sortable } +type byMarketCapAsc struct{ sortable } + +type byTickerDesc struct{ sortable } +type byLastTradeDesc struct{ sortable } +type byChangeDesc struct{ sortable } +type byChangePctDesc struct{ sortable } +type byOpenDesc struct{ sortable } +type byLowDesc struct{ sortable } +type byHighDesc struct{ sortable } +type byLow52Desc struct{ sortable } +type byHigh52Desc struct{ sortable } +type byVolumeDesc struct{ sortable } +type byAvgVolumeDesc struct{ sortable } +type byPeRatioDesc struct{ sortable } +type byDividendDesc struct{ sortable } +type byYieldDesc struct{ sortable } +type byMarketCapDesc struct{ sortable } + +func (list byTickerAsc) Less(i, j int) bool { + return list.sortable[i].Ticker < list.sortable[j].Ticker +} +func (list byLastTradeAsc) Less(i, j int) bool { + return list.sortable[i].LastTrade < list.sortable[j].LastTrade +} +func (list byChangeAsc) Less(i, j int) bool { + return c(list.sortable[i].Change) < c(list.sortable[j].Change) +} +func (list byChangePctAsc) Less(i, j int) bool { + return c(list.sortable[i].ChangePct) < c(list.sortable[j].ChangePct) +} +func (list byOpenAsc) Less(i, j int) bool { + return list.sortable[i].Open < list.sortable[j].Open +} +func (list byLowAsc) Less(i, j int) bool { return list.sortable[i].Low < list.sortable[j].Low } +func (list byHighAsc) Less(i, j int) bool { + return list.sortable[i].High < list.sortable[j].High +} +func (list byLow52Asc) Less(i, j int) bool { + return list.sortable[i].Low52 < list.sortable[j].Low52 +} +func (list byHigh52Asc) Less(i, j int) bool { + return list.sortable[i].High52 < list.sortable[j].High52 +} +func (list byVolumeAsc) Less(i, j int) bool { + return list.sortable[i].Volume < list.sortable[j].Volume +} +func (list byAvgVolumeAsc) Less(i, j int) bool { + return list.sortable[i].AvgVolume < list.sortable[j].AvgVolume +} +func (list byPeRatioAsc) Less(i, j int) bool { + return list.sortable[i].PeRatio < list.sortable[j].PeRatio +} +func (list byDividendAsc) Less(i, j int) bool { + return list.sortable[i].Dividend < list.sortable[j].Dividend +} +func (list byYieldAsc) Less(i, j int) bool { + return list.sortable[i].Yield < list.sortable[j].Yield +} +func (list byMarketCapAsc) Less(i, j int) bool { + return m(list.sortable[i].MarketCap) < m(list.sortable[j].MarketCap) +} + +func (list byTickerDesc) Less(i, j int) bool { + return list.sortable[j].Ticker < list.sortable[i].Ticker +} +func (list byLastTradeDesc) Less(i, j int) bool { + return list.sortable[j].LastTrade < list.sortable[i].LastTrade +} +func (list byChangeDesc) Less(i, j int) bool { + return c(list.sortable[j].ChangePct) < c(list.sortable[i].ChangePct) +} +func (list byChangePctDesc) Less(i, j int) bool { + return c(list.sortable[j].ChangePct) < c(list.sortable[i].ChangePct) +} +func (list byOpenDesc) Less(i, j int) bool { + return list.sortable[j].Open < list.sortable[i].Open +} +func (list byLowDesc) Less(i, j int) bool { return list.sortable[j].Low < list.sortable[i].Low } +func (list byHighDesc) Less(i, j int) bool { + return list.sortable[j].High < list.sortable[i].High +} +func (list byLow52Desc) Less(i, j int) bool { + return list.sortable[j].Low52 < list.sortable[i].Low52 +} +func (list byHigh52Desc) Less(i, j int) bool { + return list.sortable[j].High52 < list.sortable[i].High52 +} +func (list byVolumeDesc) Less(i, j int) bool { + return list.sortable[j].Volume < list.sortable[i].Volume +} +func (list byAvgVolumeDesc) Less(i, j int) bool { + return list.sortable[j].AvgVolume < list.sortable[i].AvgVolume +} +func (list byPeRatioDesc) Less(i, j int) bool { + return list.sortable[j].PeRatio < list.sortable[i].PeRatio +} +func (list byDividendDesc) Less(i, j int) bool { + return list.sortable[j].Dividend < list.sortable[i].Dividend +} +func (list byYieldDesc) Less(i, j int) bool { + return list.sortable[j].Yield < list.sortable[i].Yield +} +func (list byMarketCapDesc) Less(i, j int) bool { + return m(list.sortable[j].MarketCap) < m(list.sortable[i].MarketCap) +} // Returns new Sorter struct. func NewSorter(profile *Profile) *Sorter { @@ -99,39 +156,39 @@ func (sorter *Sorter) SortByCurrentColumn(stocks []Stock) *Sorter { if sorter.profile.Ascending { interfaces = []sort.Interface{ - byTickerAsc { stocks }, - byLastTradeAsc { stocks }, - byChangeAsc { stocks }, - byChangePctAsc { stocks }, - byOpenAsc { stocks }, - byLowAsc { stocks }, - byHighAsc { stocks }, - byLow52Asc { stocks }, - byHigh52Asc { stocks }, - byVolumeAsc { stocks }, - byAvgVolumeAsc { stocks }, - byPeRatioAsc { stocks }, - byDividendAsc { stocks }, - byYieldAsc { stocks }, - byMarketCapAsc { stocks }, + byTickerAsc{stocks}, + byLastTradeAsc{stocks}, + byChangeAsc{stocks}, + byChangePctAsc{stocks}, + byOpenAsc{stocks}, + byLowAsc{stocks}, + byHighAsc{stocks}, + byLow52Asc{stocks}, + byHigh52Asc{stocks}, + byVolumeAsc{stocks}, + byAvgVolumeAsc{stocks}, + byPeRatioAsc{stocks}, + byDividendAsc{stocks}, + byYieldAsc{stocks}, + byMarketCapAsc{stocks}, } } else { interfaces = []sort.Interface{ - byTickerDesc { stocks }, - byLastTradeDesc { stocks }, - byChangeDesc { stocks }, - byChangePctDesc { stocks }, - byOpenDesc { stocks }, - byLowDesc { stocks }, - byHighDesc { stocks }, - byLow52Desc { stocks }, - byHigh52Desc { stocks }, - byVolumeDesc { stocks }, - byAvgVolumeDesc { stocks }, - byPeRatioDesc { stocks }, - byDividendDesc { stocks }, - byYieldDesc { stocks }, - byMarketCapDesc { stocks }, + byTickerDesc{stocks}, + byLastTradeDesc{stocks}, + byChangeDesc{stocks}, + byChangePctDesc{stocks}, + byOpenDesc{stocks}, + byLowDesc{stocks}, + byHighDesc{stocks}, + byLow52Desc{stocks}, + byHigh52Desc{stocks}, + byVolumeDesc{stocks}, + byAvgVolumeDesc{stocks}, + byPeRatioDesc{stocks}, + byDividendDesc{stocks}, + byYieldDesc{stocks}, + byMarketCapDesc{stocks}, } } @@ -153,7 +210,7 @@ func c(str string) float32 { func m(str string) float32 { multiplier := 1.0 - switch str[len(str)-1:len(str)] { // Check the last character. + switch str[len(str)-1 : len(str)] { // Check the last character. case `B`: multiplier = 1000000000.0 case `M`: @@ -162,7 +219,7 @@ func m(str string) float32 { multiplier = 1000.0 } - trimmed := strings.Trim(str, ` $BMK`) // Get rid of non-numeric characters. + trimmed := strings.Trim(str, ` $BMK`) // Get rid of non-numeric characters. value, _ := strconv.ParseFloat(trimmed, 32) return float32(value * multiplier) diff --git a/yahoo_quotes.go b/yahoo_quotes.go index a20fcb0..3e1dc62 100644 --- a/yahoo_quotes.go +++ b/yahoo_quotes.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +// Copyright (c) 2013-2015 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. @@ -24,41 +24,41 @@ const quotesURL = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=sl1c1p2 // Stock stores quote information for the particular stock ticker. The data // for all the fields except 'Advancing' is fetched using Yahoo market API. type Stock struct { - Ticker string // Stock ticker. - LastTrade string // l1: last trade. - Change string // c6: change real time. - ChangePct string // k2: percent change real time. - Open string // o: market open price. - Low string // g: day's low. - High string // h: day's high. - Low52 string // j: 52-weeks low. - High52 string // k: 52-weeks high. - Volume string // v: volume. - AvgVolume string // a2: average volume. - PeRatio string // r2: P/E ration real time. - PeRatioX string // r: P/E ration (fallback when real time is N/A). - Dividend string // d: dividend. - Yield string // y: dividend yield. - MarketCap string // j3: market cap real time. - MarketCapX string // j1: market cap (fallback when real time is N/A). - Advancing bool // True when change is >= $0. + Ticker string // Stock ticker. + LastTrade string // l1: last trade. + Change string // c6: change real time. + ChangePct string // k2: percent change real time. + Open string // o: market open price. + Low string // g: day's low. + High string // h: day's high. + Low52 string // j: 52-weeks low. + High52 string // k: 52-weeks high. + Volume string // v: volume. + AvgVolume string // a2: average volume. + PeRatio string // r2: P/E ration real time. + PeRatioX string // r: P/E ration (fallback when real time is N/A). + Dividend string // d: dividend. + Yield string // y: dividend yield. + MarketCap string // j3: market cap real time. + MarketCapX string // j1: market cap (fallback when real time is N/A). + Advancing bool // True when change is >= $0. } // Quotes stores relevant pointers as well as the array of stock quotes for // the tickers we are tracking. type Quotes struct { - market *Market // Pointer to Market. - profile *Profile // Pointer to Profile. - stocks []Stock // Array of stock quote data. - errors string // Error string if any. + market *Market // Pointer to Market. + profile *Profile // Pointer to Profile. + stocks []Stock // Array of stock quote data. + errors string // Error string if any. } // Sets the initial values and returns new Quotes struct. func NewQuotes(market *Market, profile *Profile) *Quotes { return &Quotes{ - market: market, + market: market, profile: profile, - errors: ``, + errors: ``, } } @@ -102,7 +102,7 @@ func (quotes *Quotes) Ok() (bool, string) { // when user adds new stock tickers. func (quotes *Quotes) AddTickers(tickers []string) (added int, err error) { if added, err = quotes.profile.AddTickers(tickers); err == nil && added > 0 { - quotes.stocks = nil // Force fetch. + quotes.stocks = nil // Force fetch. } return } @@ -112,7 +112,7 @@ func (quotes *Quotes) AddTickers(tickers []string) (added int, err error) { // when user removes existing stock tickers. func (quotes *Quotes) RemoveTickers(tickers []string) (removed int, err error) { if removed, err = quotes.profile.RemoveTickers(tickers); err == nil && removed > 0 { - quotes.stocks = nil // Force fetch. + quotes.stocks = nil // Force fetch. } return }