master
Michael Dvorkin 10 years ago
parent 7932776cf5
commit bc666ec165
  1. 2
      LICENSE
  2. 2
      README.md
  3. 4
      cmd/mop.go
  4. 90
      cnn_market.go
  5. 21
      column_editor.go
  6. 78
      layout.go
  7. 44
      line_editor.go
  8. 54
      markup.go
  9. 34
      profile.go
  10. 38
      screen.go
  11. 253
      sorter.go
  12. 54
      yahoo_quotes.go

@ -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 Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

@ -44,7 +44,7 @@ comments, suggestions, and contributions are welcome.
### License ### ### License ###
Copyright (c) 2013 Michael Dvorkin. All Rights Reserved. Copyright (c) 2013-2015 Michael Dvorkin. All Rights Reserved.
"mike" + "@dvorkin" + ".net" || "twitter.com/mid" "mike" + "@dvorkin" + ".net" || "twitter.com/mid"
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -10,7 +10,7 @@ import (
`time` `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. NO WARRANTIES OF ANY KIND WHATSOEVER. SEE THE LICENSE FILE FOR DETAILS.
<u>Command</u> <u>Description </u> <u>Command</u> <u>Description </u>

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // 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 // 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. // the screen. The market data is fetched and parsed from the HTML page above.
type Market struct { type Market struct {
IsClosed bool // True when U.S. markets are closed. IsClosed bool // True when U.S. markets are closed.
Dow map[string]string // Hash of Dow Jones indicators. Dow map[string]string // Hash of Dow Jones indicators.
Nasdaq map[string]string // Hash of NASDAQ indicators. Nasdaq map[string]string // Hash of NASDAQ indicators.
Sp500 map[string]string // Hash of S&P 500 indicators. Sp500 map[string]string // Hash of S&P 500 indicators.
Tokyo map[string]string Tokyo map[string]string
HongKong map[string]string HongKong map[string]string
London map[string]string London map[string]string
Frankfurt map[string]string Frankfurt map[string]string
Yield map[string]string Yield map[string]string
Oil map[string]string Oil map[string]string
Yen map[string]string Yen map[string]string
Euro map[string]string Euro map[string]string
Gold map[string]string Gold map[string]string
regex *regexp.Regexp // Regex to parse market data from HTML. regex *regexp.Regexp // Regex to parse market data from HTML.
errors string // Error(s), if any. errors string // Error(s), if any.
} }
// Returns new initialized Market struct. // Returns new initialized Market struct.
func NewMarket() *Market { func NewMarket() *Market {
market := &Market{}; market := &Market{}
market.IsClosed = false market.IsClosed = false
market.Dow = make(map[string]string) market.Dow = make(map[string]string)
market.Nasdaq = make(map[string]string) market.Nasdaq = make(map[string]string)
market.Sp500 = make(map[string]string) market.Sp500 = make(map[string]string)
market.Tokyo = make(map[string]string) market.Tokyo = make(map[string]string)
market.HongKong = make(map[string]string) market.HongKong = make(map[string]string)
market.London = make(map[string]string) market.London = make(map[string]string)
market.Frankfurt = make(map[string]string) market.Frankfurt = make(map[string]string)
market.Yield = make(map[string]string) market.Yield = make(map[string]string)
market.Oil = make(map[string]string) market.Oil = make(map[string]string)
market.Yen = make(map[string]string) market.Yen = make(map[string]string)
market.Euro = make(map[string]string) market.Euro = make(map[string]string)
market.Gold = make(map[string]string) market.Gold = make(map[string]string)
market.errors = `` market.errors = ``
const any = `\s*(?:.+?)` const any = `\s*(?:.+?)`
const price = `>([\d\.,]+)</span>` const price = `>([\d\.,]+)</span>`
const percent = `>([\+\-]?[\d\.,]+%?)<` const percent = `>([\+\-]?[\d\.,]+%?)<`
rules := []string{ rules := []string{
`>Dow<`, any, percent, any, price, any, percent, any, `>Dow<`, any, percent, any, price, any, percent, any,
`>Nasdaq<`, any, percent, any, price, any, percent, any, `>Nasdaq<`, any, percent, any, price, any, percent, any,
`">S&P<`, 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, `>Nikkei 225<`, any, percent, any, price, any, percent, any,
`>Hang Seng<`, 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, `>FTSE 100<`, any, percent, any, price, any, percent, any,
`>DAX<`, any, percent, any, price, any, percent, any, `>DAX<`, any, percent, any, price, any, percent, any,
`>10-year yield<`, any, price, any, percent, any, `>10-year yield<`, any, price, any, percent, any,
`>Oil<`, any, price, any, percent, any, `>Oil<`, any, price, any, percent, any,
`>Yen<`, any, price, any, percent, any, `>Yen<`, any, price, any, percent, any,
`>Euro<`, any, price, any, percent, any, `>Euro<`, any, price, any, percent, any,
`>Gold<`, any, price, any, percent, any, `>Gold<`, any, price, any, percent, any,
} }
market.regex = regexp.MustCompile(strings.Join(rules, ``)) 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 { func (market *Market) extract(snippet []byte) *Market {
matches := market.regex.FindStringSubmatch(string(snippet)) matches := market.regex.FindStringSubmatch(string(snippet))
if len(matches) < 31 { if len(matches) < 31 {
panic(`Unable to parse ` + marketURL) panic(`Unable to parse ` + marketURL)
} }
market.Dow[`change`] = matches[1] market.Dow[`change`] = matches[1]
market.Dow[`latest`] = matches[2] market.Dow[`latest`] = matches[2]

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // 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 // 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.
layout *Layout // Pointer to Layout to redraw stock quotes header. layout *Layout // Pointer to Layout to redraw stock quotes header.
profile *Profile // Pointer to Profile where we save newly selected sort order. profile *Profile // Pointer to Profile where we save newly selected sort order.
} }
// Returns new initialized ColumnEditor struct. As part of initialization it // Returns new initialized ColumnEditor struct. As part of initialization it
// highlights current column name (as stored in Profile). // highlights current column name (as stored in Profile).
func NewColumnEditor(screen *Screen, quotes *Quotes) *ColumnEditor { func NewColumnEditor(screen *Screen, quotes *Quotes) *ColumnEditor {
editor := &ColumnEditor{ editor := &ColumnEditor{
screen: screen, screen: screen,
quotes: quotes, quotes: quotes,
layout: screen.layout, layout: screen.layout,
profile: quotes.profile, profile: quotes.profile,
} }
@ -43,7 +43,7 @@ func (editor *ColumnEditor) Handle(event termbox.Event) bool {
case termbox.KeyEnter: case termbox.KeyEnter:
editor.execute() editor.execute()
case termbox.KeyArrowLeft: case termbox.KeyArrowLeft:
editor.selectLeftColumn() editor.selectLeftColumn()
case termbox.KeyArrowRight: case termbox.KeyArrowRight:
@ -72,7 +72,7 @@ func (editor *ColumnEditor) selectLeftColumn() *ColumnEditor {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *ColumnEditor) selectRightColumn() *ColumnEditor { func (editor *ColumnEditor) selectRightColumn() *ColumnEditor {
editor.profile.selectedColumn++ editor.profile.selectedColumn++
if editor.profile.selectedColumn > editor.layout.TotalColumns() - 1 { if editor.profile.selectedColumn > editor.layout.TotalColumns()-1 {
editor.profile.selectedColumn = 0 editor.profile.selectedColumn = 0
} }
return editor return editor
@ -98,4 +98,3 @@ func (editor *ColumnEditor) redrawHeader() {
editor.screen.DrawLine(0, 4, editor.layout.Header(editor.profile)) editor.screen.DrawLine(0, 4, editor.layout.Header(editor.profile))
termbox.Flush() termbox.Flush()
} }

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -17,41 +17,41 @@ import (
// Column describes formatting rules for individual column within the list // Column describes formatting rules for individual column within the list
// of stock quotes. // of stock quotes.
type Column struct { type Column struct {
width int // Column width. width int // Column width.
name string // The name of the field in the Stock struct. name string // The name of the field in the Stock struct.
title string // Column title to display in the header. title string // Column title to display in the header.
formatter func(string)string // Optional function to format the contents of the column. 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 // Layout is used to format and display all the collected data, i.e. market
// updates and the list of stock quotes. // updates and the list of stock quotes.
type Layout struct { type Layout struct {
columns []Column // List of stock quotes columns. columns []Column // List of stock quotes columns.
sorter *Sorter // Pointer to sorting receiver. sorter *Sorter // Pointer to sorting receiver.
regex *regexp.Regexp // Pointer to regular expression to align decimal points. regex *regexp.Regexp // Pointer to regular expression to align decimal points.
marketTemplate *template.Template // Pointer to template to format market data. marketTemplate *template.Template // Pointer to template to format market data.
quotesTemplate *template.Template // Pointer to template to format the list of stock quotes. quotesTemplate *template.Template // Pointer to template to format the list of stock quotes.
} }
// Creates the layout and assigns the default values that stay unchanged. // Creates the layout and assigns the default values that stay unchanged.
func NewLayout() *Layout { func NewLayout() *Layout {
layout := &Layout{} layout := &Layout{}
layout.columns = []Column{ layout.columns = []Column{
{ -7, `Ticker`, `Ticker`, nil }, {-7, `Ticker`, `Ticker`, nil},
{ 10, `LastTrade`, `Last`, currency }, {10, `LastTrade`, `Last`, currency},
{ 10, `Change`, `Change`, currency }, {10, `Change`, `Change`, currency},
{ 10, `ChangePct`, `Change%`, last }, {10, `ChangePct`, `Change%`, last},
{ 10, `Open`, `Open`, currency }, {10, `Open`, `Open`, currency},
{ 10, `Low`, `Low`, currency }, {10, `Low`, `Low`, currency},
{ 10, `High`, `High`, currency }, {10, `High`, `High`, currency},
{ 10, `Low52`, `52w Low`, currency }, {10, `Low52`, `52w Low`, currency},
{ 10, `High52`, `52w High`, currency }, {10, `High52`, `52w High`, currency},
{ 11, `Volume`, `Volume`, nil }, {11, `Volume`, `Volume`, nil},
{ 11, `AvgVolume`, `AvgVolume`, nil }, {11, `AvgVolume`, `AvgVolume`, nil},
{ 9, `PeRatio`, `P/E`, blank }, {9, `PeRatio`, `P/E`, blank},
{ 9, `Dividend`, `Dividend`, zero }, {9, `Dividend`, `Dividend`, zero},
{ 9, `Yield`, `Yield`, percent }, {9, `Yield`, `Yield`, percent},
{ 11, `MarketCap`, `MktCap`, currency }, {11, `MarketCap`, `MktCap`, currency},
} }
layout.regex = regexp.MustCompile(`(\.\d+)[BMK]?$`) layout.regex = regexp.MustCompile(`(\.\d+)[BMK]?$`)
layout.marketTemplate = buildMarketTemplate() layout.marketTemplate = buildMarketTemplate()
@ -63,8 +63,8 @@ func NewLayout() *Layout {
// Market merges given market data structure with the market template and // Market merges given market data structure with the market template and
// returns formatted string that includes highlighting markup. // returns formatted string that includes highlighting markup.
func (layout *Layout) Market(market *Market) string { func (layout *Layout) Market(market *Market) string {
if ok, err := market.Ok(); !ok { // If there was an error fetching market data... if ok, err := market.Ok(); !ok { // If there was an error fetching market data...
return err // then simply return the error string. return err // then simply return the error string.
} }
highlight(market.Dow, market.Sp500, market.Nasdaq, 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 // and the list of given stock quotes. It returns formatted string with
// all the necessary markup. // all the necessary markup.
func (layout *Layout) Quotes(quotes *Quotes) string { func (layout *Layout) Quotes(quotes *Quotes) string {
if ok, err := quotes.Ok(); !ok { // If there was an error fetching stock quotes... if ok, err := quotes.Ok(); !ok { // If there was an error fetching stock quotes...
return err // then simply return the error string. return err // then simply return the error string.
} }
vars := struct { vars := struct {
Now string // Current timestamp. Now string // Current timestamp.
Header string // Formatted header line. Header string // Formatted header line.
Stocks []Stock // List of formatted stock quotes. Stocks []Stock // List of formatted stock quotes.
}{ }{
time.Now().Format(`3:04:05pm PST`), time.Now().Format(`3:04:05pm PST`),
layout.Header(quotes.profile), layout.Header(quotes.profile),
@ -107,12 +107,12 @@ func (layout *Layout) Quotes(quotes *Quotes) string {
func (layout *Layout) Header(profile *Profile) string { func (layout *Layout) Header(profile *Profile) string {
str, selectedColumn := ``, profile.selectedColumn str, selectedColumn := ``, profile.selectedColumn
for i,col := range layout.columns { for i, col := range layout.columns {
arrow := arrowFor(i, profile) arrow := arrowFor(i, profile)
if i != selectedColumn { 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)
} }
} }
@ -139,7 +139,7 @@ func (layout *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 layout.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 {
@ -173,7 +173,7 @@ func (layout *Layout) pad(str string, width int) string {
if len(match) > 0 { if len(match) > 0 {
switch len(match[1]) { switch len(match[1]) {
case 2: 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: case 4, 5:
str = strings.Replace(str, match[1], match[1][0:3], 1) 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)) grouped := make([]Stock, len(stocks))
current := 0 current := 0
for _,stock := range stocks { for _, stock := range stocks {
if stock.Advancing { if stock.Advancing {
grouped[current] = stock grouped[current] = stock
current++ current++
} }
} }
for _,stock := range stocks { for _, stock := range stocks {
if !stock.Advancing { if !stock.Advancing {
grouped[current] = stock grouped[current] = stock
current++ current++

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -15,13 +15,13 @@ import (
// data and keep track of cursor movements (left, right, beginning of the // data and keep track of cursor movements (left, right, beginning of the
// line, end of the line, and backspace). // line, end of the line, and backspace).
type LineEditor struct { type LineEditor struct {
command rune // Keyboard command such as '+' or '-'. command rune // Keyboard command such as '+' or '-'.
cursor int // Current cursor position within the input line. cursor int // Current cursor position within the input line.
prompt string // Prompt string for the command. prompt string // Prompt string for the command.
input string // User typed input string. input string // User typed input string.
screen *Screen // Pointer to Screen. screen *Screen // Pointer to Screen.
quotes *Quotes // Pointer to Quotes. quotes *Quotes // Pointer to Quotes.
regex *regexp.Regexp // Regex to split comma-delimited input string. regex *regexp.Regexp // Regex to split comma-delimited input string.
} }
// Returns new initialized LineEditor struct. // Returns new initialized LineEditor struct.
@ -29,7 +29,7 @@ func NewLineEditor(screen *Screen, quotes *Quotes) *LineEditor {
return &LineEditor{ return &LineEditor{
screen: screen, screen: screen,
quotes: quotes, 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.prompt = prompt
editor.command = command editor.command = command
editor.screen.DrawLine(0, 3, `<white>` + editor.prompt + `</>`) editor.screen.DrawLine(0, 3, `<white>`+editor.prompt+`</>`)
termbox.SetCursor(len(editor.prompt), 3) termbox.SetCursor(len(editor.prompt), 3)
termbox.Flush() termbox.Flush()
} }
@ -65,7 +65,7 @@ func (editor *LineEditor) Handle(ev termbox.Event) bool {
case termbox.KeyEnter: case termbox.KeyEnter:
return editor.execute().done() return editor.execute().done()
case termbox.KeyBackspace, termbox.KeyBackspace2: case termbox.KeyBackspace, termbox.KeyBackspace2:
editor.deletePreviousCharacter() editor.deletePreviousCharacter()
case termbox.KeyCtrlB, termbox.KeyArrowLeft: case termbox.KeyCtrlB, termbox.KeyArrowLeft:
@ -97,12 +97,12 @@ func (editor *LineEditor) deletePreviousCharacter() *LineEditor {
if editor.cursor > 0 { if editor.cursor > 0 {
if editor.cursor < len(editor.input) { if editor.cursor < len(editor.input) {
// Remove character in the middle of the input string. // 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 { } else {
// Remove last input character. // 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() editor.moveLeft()
} }
@ -113,7 +113,7 @@ func (editor *LineEditor) deletePreviousCharacter() *LineEditor {
func (editor *LineEditor) insertCharacter(ch rune) *LineEditor { func (editor *LineEditor) insertCharacter(ch rune) *LineEditor {
if editor.cursor < len(editor.input) { if editor.cursor < len(editor.input) {
// Insert the character in the middle of the input string. // 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 { } else {
// Append the character to the end of the input string. // Append the character to the end of the input string.
editor.input += string(ch) editor.input += string(ch)
@ -128,7 +128,7 @@ func (editor *LineEditor) insertCharacter(ch rune) *LineEditor {
func (editor *LineEditor) moveLeft() *LineEditor { func (editor *LineEditor) moveLeft() *LineEditor {
if editor.cursor > 0 { if editor.cursor > 0 {
editor.cursor-- editor.cursor--
termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) termbox.SetCursor(len(editor.prompt)+editor.cursor, 3)
} }
return editor return editor
@ -138,7 +138,7 @@ func (editor *LineEditor) moveLeft() *LineEditor {
func (editor *LineEditor) moveRight() *LineEditor { func (editor *LineEditor) moveRight() *LineEditor {
if editor.cursor < len(editor.input) { if editor.cursor < len(editor.input) {
editor.cursor++ editor.cursor++
termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) termbox.SetCursor(len(editor.prompt)+editor.cursor, 3)
} }
return editor return editor
@ -147,7 +147,7 @@ func (editor *LineEditor) moveRight() *LineEditor {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *LineEditor) jumpToBeginning() *LineEditor { func (editor *LineEditor) jumpToBeginning() *LineEditor {
editor.cursor = 0 editor.cursor = 0
termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) termbox.SetCursor(len(editor.prompt)+editor.cursor, 3)
return editor return editor
} }
@ -155,7 +155,7 @@ func (editor *LineEditor) jumpToBeginning() *LineEditor {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (editor *LineEditor) jumpToEnd() *LineEditor { func (editor *LineEditor) jumpToEnd() *LineEditor {
editor.cursor = len(editor.input) editor.cursor = len(editor.input)
termbox.SetCursor(len(editor.prompt) + editor.cursor, 3) termbox.SetCursor(len(editor.prompt)+editor.cursor, 3)
return editor return editor
} }
@ -166,7 +166,7 @@ func (editor *LineEditor) execute() *LineEditor {
case '+': case '+':
tickers := editor.tokenize() tickers := editor.tokenize()
if len(tickers) > 0 { 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) editor.screen.Draw(editor.quotes)
} }
} }
@ -174,13 +174,13 @@ func (editor *LineEditor) execute() *LineEditor {
tickers := editor.tokenize() tickers := editor.tokenize()
if len(tickers) > 0 { if len(tickers) > 0 {
before := len(editor.quotes.profile.Tickers) 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) editor.screen.Draw(editor.quotes)
// Clear the lines at the bottom of the list, if any. // Clear the lines at the bottom of the list, if any.
after := before - removed after := before - removed
for i := before; i > after; i-- { for i := before; i > after; i-- {
editor.screen.ClearLine(0, i + 4) editor.screen.ClearLine(0, i+4)
} }
} }
} }

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -24,36 +24,36 @@ import (
// The <right>...</right> tag is used to right align the enclosed string // The <right>...</right> tag is used to right align the enclosed string
// (ex. when displaying current time in the upper right corner). // (ex. when displaying current time in the upper right corner).
type Markup struct { type Markup struct {
Foreground termbox.Attribute // Foreground color. Foreground termbox.Attribute // Foreground color.
Background termbox.Attribute // Background color (so far always termbox.ColorDefault). Background termbox.Attribute // Background color (so far always termbox.ColorDefault).
RightAligned bool // True when the string is right aligned. RightAligned bool // True when the string is right aligned.
tags map[string]termbox.Attribute // Tags to Termbox translation hash. tags map[string]termbox.Attribute // Tags to Termbox translation hash.
regex *regexp.Regexp // Regex to identify the supported tag names. regex *regexp.Regexp // Regex to identify the supported tag names.
} }
// Creates markup to define tag to Termbox translation rules and store default // Creates markup to define tag to Termbox translation rules and store default
// colors and column alignments. // colors and column alignments.
func NewMarkup() *Markup { func NewMarkup() *Markup {
markup := &Markup{} markup := &Markup{}
markup.Foreground = termbox.ColorDefault markup.Foreground = termbox.ColorDefault
markup.Background = termbox.ColorDefault markup.Background = termbox.ColorDefault
markup.RightAligned = false markup.RightAligned = false
markup.tags = make(map[string]termbox.Attribute) markup.tags = make(map[string]termbox.Attribute)
markup.tags[`/`] = termbox.ColorDefault markup.tags[`/`] = termbox.ColorDefault
markup.tags[`black`] = termbox.ColorBlack markup.tags[`black`] = termbox.ColorBlack
markup.tags[`red`] = termbox.ColorRed markup.tags[`red`] = termbox.ColorRed
markup.tags[`green`] = termbox.ColorGreen markup.tags[`green`] = termbox.ColorGreen
markup.tags[`yellow`] = termbox.ColorYellow markup.tags[`yellow`] = termbox.ColorYellow
markup.tags[`blue`] = termbox.ColorBlue markup.tags[`blue`] = termbox.ColorBlue
markup.tags[`magenta`] = termbox.ColorMagenta markup.tags[`magenta`] = termbox.ColorMagenta
markup.tags[`cyan`] = termbox.ColorCyan markup.tags[`cyan`] = termbox.ColorCyan
markup.tags[`white`] = termbox.ColorWhite markup.tags[`white`] = termbox.ColorWhite
markup.tags[`right`] = termbox.ColorDefault // Termbox can combine attributes and a single color using bitwise OR. 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[`b`] = termbox.AttrBold // Attribute = 1 << (iota + 4)
markup.tags[`u`] = termbox.AttrUnderline markup.tags[`u`] = termbox.AttrUnderline
markup.tags[`r`] = termbox.AttrReverse markup.tags[`r`] = termbox.AttrReverse
markup.regex = markup.supportedTags() // Once we have the hash we could build the regex. markup.regex = markup.supportedTags() // Once we have the hash we could build the regex.
return markup return markup
} }
@ -111,17 +111,17 @@ func (markup *Markup) process(tag string, open bool) bool {
if attribute, ok := markup.tags[tag]; ok { if attribute, ok := markup.tags[tag]; ok {
switch tag { switch tag {
case `right`: case `right`:
markup.RightAligned = open // On for <right>, off for </right>. markup.RightAligned = open // On for <right>, off for </right>.
default: default:
if open { if open {
if attribute >= termbox.AttrBold { if attribute >= termbox.AttrBold {
markup.Foreground |= attribute // Set the Termbox attribute. markup.Foreground |= attribute // Set the Termbox attribute.
} else { } else {
markup.Foreground = attribute // Set the Termbox color. markup.Foreground = attribute // Set the Termbox color.
} }
} else { } else {
if attribute >= termbox.AttrBold { if attribute >= termbox.AttrBold {
markup.Foreground &= ^attribute // Clear the Termbox attribute. markup.Foreground &= ^attribute // Clear the Termbox attribute.
} else { } else {
markup.Foreground = termbox.ColorDefault markup.Foreground = termbox.ColorDefault
} }
@ -138,7 +138,7 @@ func (markup *Markup) supportedTags() *regexp.Regexp {
arr := []string{} arr := []string{}
for tag := range markup.tags { for tag := range markup.tags {
arr = append(arr, `</?` + tag + `>`) arr = append(arr, `</?`+tag+`>`)
} }
return regexp.MustCompile(strings.Join(arr, `|`)) return regexp.MustCompile(strings.Join(arr, `|`))

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -18,13 +18,13 @@ const moprc = `/.moprc`
// stock tickers). The settings are serialized using JSON and saved in // stock tickers). The settings are serialized using JSON and saved in
// the ~/.moprc file. // the ~/.moprc file.
type Profile struct { type Profile struct {
Tickers []string // List of stock tickers to display. Tickers []string // List of stock tickers to display.
MarketRefresh int // Time interval to refresh market data. MarketRefresh int // Time interval to refresh market data.
QuotesRefresh int // Time interval to refresh stock quotes. QuotesRefresh int // Time interval to refresh stock quotes.
SortColumn int // Column number by which we sort stock quotes. SortColumn int // Column number by which we sort stock quotes.
Ascending bool // True when sort order is ascending. Ascending bool // True when sort order is ascending.
Grouped bool // True when stocks are grouped by advancing/declining. Grouped bool // True when stocks are grouped by advancing/declining.
selectedColumn int // Stores selected column number when the column editor is active. selectedColumn int // Stores selected column number when the column editor is active.
} }
// Creates the profile and attempts to load the settings from ~/.moprc file. // Creates the profile and attempts to load the settings from ~/.moprc file.
@ -32,13 +32,13 @@ type Profile struct {
func NewProfile() *Profile { func NewProfile() *Profile {
profile := &Profile{} profile := &Profile{}
data, err := ioutil.ReadFile(profile.defaultFileName()) data, err := ioutil.ReadFile(profile.defaultFileName())
if err != nil { // Set default values: if err != nil { // Set default values:
profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute). 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.QuotesRefresh = 5 // Stock quotes get updated every 5s (12 times per minute).
profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining. profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining.
profile.Tickers = []string{ `AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V` } profile.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`}
profile.SortColumn = 0 // Stock quotes are sorted by ticker name. profile.SortColumn = 0 // Stock quotes are sorted by ticker name.
profile.Ascending = true // A to Z. profile.Ascending = true // A to Z.
profile.Save() profile.Save()
} else { } else {
json.Unmarshal(data, profile) 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. // for the current column, or to pick another sort column.
func (profile *Profile) Reorder() error { func (profile *Profile) Reorder() error {
if profile.selectedColumn == profile.SortColumn { if profile.selectedColumn == profile.SortColumn {
profile.Ascending = !profile.Ascending // Reverse sort order. profile.Ascending = !profile.Ascending // Reverse sort order.
} else { } else {
profile.SortColumn = profile.selectedColumn // Pick new sort column. profile.SortColumn = profile.selectedColumn // Pick new sort column.
} }
return profile.Save() return profile.Save()
} }

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -13,12 +13,12 @@ import (
// Screen is thin wrapper aroung Termbox library to provide basic display // Screen is thin wrapper aroung Termbox library to provide basic display
// capabilities as requied by Mop. // capabilities as requied by Mop.
type Screen struct { type Screen struct {
width int // Current number of columns. width int // Current number of columns.
height int // Current number of rows. height int // Current number of rows.
cleared bool // True after the screens gets cleared. cleared bool // True after the screens gets cleared.
layout *Layout // Pointer to layout (gets created by screen). layout *Layout // Pointer to layout (gets created by screen).
markup *Markup // Pointer to markup processor (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. pausedAt *time.Time // Timestamp of the pause request or nil if none.
} }
// Initializes Termbox, creates screen along with layout and markup, and // 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 // Pause is a toggle function that either creates a timestamp of the pause
// request or resets it to nil. // request or resets it to nil.
func (screen *Screen) Pause(pause bool) *Screen { func (screen *Screen) Pause(pause bool) *Screen {
if pause { if pause {
screen.pausedAt = new(time.Time) screen.pausedAt = new(time.Time)
*screen.pausedAt = time.Now() *screen.pausedAt = time.Now()
} else { } else {
screen.pausedAt = nil screen.pausedAt = nil
} }
return screen return screen
} }
// Clear makes the entire screen blank using default background color. // 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 // Draw accepts variable number of arguments and knows how to display the
// market data, stock quotes, current time, and an arbitrary string. // market data, stock quotes, current time, and an arbitrary string.
func (screen *Screen) Draw(objects ...interface{}) *Screen { func (screen *Screen) Draw(objects ...interface{}) *Screen {
if screen.pausedAt != nil { if screen.pausedAt != nil {
defer screen.DrawLine(0, 0, `<right><r>` + screen.pausedAt.Format(`3:04:05pm PST`) + `</r></right>`) defer screen.DrawLine(0, 0, `<right><r>`+screen.pausedAt.Format(`3:04:05pm PST`)+`</r></right>`)
} }
for _, ptr := range objects { for _, ptr := range objects {
switch ptr.(type) { switch ptr.(type) {
case *Market: case *Market:
@ -99,7 +99,7 @@ func (screen *Screen) Draw(objects ...interface{}) *Screen {
screen.draw(screen.layout.Quotes(object.Fetch())) screen.draw(screen.layout.Quotes(object.Fetch()))
case time.Time: case time.Time:
timestamp := ptr.(time.Time).Format(`3:04:05pm PST`) timestamp := ptr.(time.Time).Format(`3:04:05pm PST`)
screen.DrawLine(0, 0, `<right>` + timestamp + `</right>`) screen.DrawLine(0, 0, `<right>`+timestamp+`</right>`)
default: default:
screen.draw(ptr.(string)) screen.draw(ptr.(string))
} }

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -14,76 +14,133 @@ import (
// setup is rather lengthy; there should probably be more concise way // setup is rather lengthy; there should probably be more concise way
// that uses reflection and avoids hardcoding the column names. // that uses reflection and avoids hardcoding the column names.
type Sorter struct { 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 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] } func (list sortable) Swap(i, j int) { list[i], list[j] = list[j], list[i] }
type byTickerAsc struct { sortable } type byTickerAsc struct{ sortable }
type byLastTradeAsc struct { sortable } type byLastTradeAsc struct{ sortable }
type byChangeAsc struct { sortable } type byChangeAsc struct{ sortable }
type byChangePctAsc struct { sortable } type byChangePctAsc struct{ sortable }
type byOpenAsc struct { sortable } type byOpenAsc struct{ sortable }
type byLowAsc struct { sortable } type byLowAsc struct{ sortable }
type byHighAsc struct { sortable } type byHighAsc struct{ sortable }
type byLow52Asc struct { sortable } type byLow52Asc struct{ sortable }
type byHigh52Asc struct { sortable } type byHigh52Asc struct{ sortable }
type byVolumeAsc struct { sortable } type byVolumeAsc struct{ sortable }
type byAvgVolumeAsc struct { sortable } type byAvgVolumeAsc struct{ sortable }
type byPeRatioAsc struct { sortable } type byPeRatioAsc struct{ sortable }
type byDividendAsc struct { sortable } type byDividendAsc struct{ sortable }
type byYieldAsc struct { sortable } type byYieldAsc struct{ sortable }
type byMarketCapAsc struct { sortable } type byMarketCapAsc struct{ sortable }
type byTickerDesc struct { sortable } type byTickerDesc struct{ sortable }
type byLastTradeDesc struct { sortable } type byLastTradeDesc struct{ sortable }
type byChangeDesc struct { sortable } type byChangeDesc struct{ sortable }
type byChangePctDesc struct { sortable } type byChangePctDesc struct{ sortable }
type byOpenDesc struct { sortable } type byOpenDesc struct{ sortable }
type byLowDesc struct { sortable } type byLowDesc struct{ sortable }
type byHighDesc struct { sortable } type byHighDesc struct{ sortable }
type byLow52Desc struct { sortable } type byLow52Desc struct{ sortable }
type byHigh52Desc struct { sortable } type byHigh52Desc struct{ sortable }
type byVolumeDesc struct { sortable } type byVolumeDesc struct{ sortable }
type byAvgVolumeDesc struct { sortable } type byAvgVolumeDesc struct{ sortable }
type byPeRatioDesc struct { sortable } type byPeRatioDesc struct{ sortable }
type byDividendDesc struct { sortable } type byDividendDesc struct{ sortable }
type byYieldDesc struct { sortable } type byYieldDesc struct{ sortable }
type byMarketCapDesc 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 byTickerAsc) Less(i, j int) bool {
func (list byLastTradeAsc) Less(i, j int) bool { return list.sortable[i].LastTrade < list.sortable[j].LastTrade } return list.sortable[i].Ticker < list.sortable[j].Ticker
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 byLastTradeAsc) Less(i, j int) bool {
func (list byOpenAsc) Less(i, j int) bool { return list.sortable[i].Open < list.sortable[j].Open } return list.sortable[i].LastTrade < list.sortable[j].LastTrade
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 byChangeAsc) Less(i, j int) bool {
func (list byLow52Asc) Less(i, j int) bool { return list.sortable[i].Low52 < list.sortable[j].Low52 } return c(list.sortable[i].Change) < c(list.sortable[j].Change)
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 byChangePctAsc) Less(i, j int) bool {
func (list byAvgVolumeAsc) Less(i, j int) bool { return list.sortable[i].AvgVolume < list.sortable[j].AvgVolume } return c(list.sortable[i].ChangePct) < c(list.sortable[j].ChangePct)
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 byOpenAsc) Less(i, j int) bool {
func (list byYieldAsc) Less(i, j int) bool { return list.sortable[i].Yield < list.sortable[j].Yield } return list.sortable[i].Open < list.sortable[j].Open
func (list byMarketCapAsc) Less(i, j int) bool { return m(list.sortable[i].MarketCap) < m(list.sortable[j].MarketCap) } }
func (list byLowAsc) Less(i, j int) bool { return list.sortable[i].Low < list.sortable[j].Low }
func (list byTickerDesc) Less(i, j int) bool { return list.sortable[j].Ticker < list.sortable[i].Ticker } func (list byHighAsc) Less(i, j int) bool {
func (list byLastTradeDesc) Less(i, j int) bool { return list.sortable[j].LastTrade < list.sortable[i].LastTrade } return list.sortable[i].High < list.sortable[j].High
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 byLow52Asc) Less(i, j int) bool {
func (list byOpenDesc) Less(i, j int) bool { return list.sortable[j].Open < list.sortable[i].Open } return list.sortable[i].Low52 < list.sortable[j].Low52
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 byHigh52Asc) Less(i, j int) bool {
func (list byLow52Desc) Less(i, j int) bool { return list.sortable[j].Low52 < list.sortable[i].Low52 } return list.sortable[i].High52 < list.sortable[j].High52
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 byVolumeAsc) Less(i, j int) bool {
func (list byAvgVolumeDesc) Less(i, j int) bool { return list.sortable[j].AvgVolume < list.sortable[i].AvgVolume } return list.sortable[i].Volume < list.sortable[j].Volume
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 byAvgVolumeAsc) Less(i, j int) bool {
func (list byYieldDesc) Less(i, j int) bool { return list.sortable[j].Yield < list.sortable[i].Yield } return list.sortable[i].AvgVolume < list.sortable[j].AvgVolume
func (list byMarketCapDesc) Less(i, j int) bool { return m(list.sortable[j].MarketCap) < m(list.sortable[i].MarketCap) } }
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. // Returns new Sorter struct.
func NewSorter(profile *Profile) *Sorter { func NewSorter(profile *Profile) *Sorter {
@ -99,39 +156,39 @@ func (sorter *Sorter) SortByCurrentColumn(stocks []Stock) *Sorter {
if sorter.profile.Ascending { if sorter.profile.Ascending {
interfaces = []sort.Interface{ interfaces = []sort.Interface{
byTickerAsc { stocks }, byTickerAsc{stocks},
byLastTradeAsc { stocks }, byLastTradeAsc{stocks},
byChangeAsc { stocks }, byChangeAsc{stocks},
byChangePctAsc { stocks }, byChangePctAsc{stocks},
byOpenAsc { stocks }, byOpenAsc{stocks},
byLowAsc { stocks }, byLowAsc{stocks},
byHighAsc { stocks }, byHighAsc{stocks},
byLow52Asc { stocks }, byLow52Asc{stocks},
byHigh52Asc { stocks }, byHigh52Asc{stocks},
byVolumeAsc { stocks }, byVolumeAsc{stocks},
byAvgVolumeAsc { stocks }, byAvgVolumeAsc{stocks},
byPeRatioAsc { stocks }, byPeRatioAsc{stocks},
byDividendAsc { stocks }, byDividendAsc{stocks},
byYieldAsc { stocks }, byYieldAsc{stocks},
byMarketCapAsc { stocks }, byMarketCapAsc{stocks},
} }
} else { } else {
interfaces = []sort.Interface{ interfaces = []sort.Interface{
byTickerDesc { stocks }, byTickerDesc{stocks},
byLastTradeDesc { stocks }, byLastTradeDesc{stocks},
byChangeDesc { stocks }, byChangeDesc{stocks},
byChangePctDesc { stocks }, byChangePctDesc{stocks},
byOpenDesc { stocks }, byOpenDesc{stocks},
byLowDesc { stocks }, byLowDesc{stocks},
byHighDesc { stocks }, byHighDesc{stocks},
byLow52Desc { stocks }, byLow52Desc{stocks},
byHigh52Desc { stocks }, byHigh52Desc{stocks},
byVolumeDesc { stocks }, byVolumeDesc{stocks},
byAvgVolumeDesc { stocks }, byAvgVolumeDesc{stocks},
byPeRatioDesc { stocks }, byPeRatioDesc{stocks},
byDividendDesc { stocks }, byDividendDesc{stocks},
byYieldDesc { stocks }, byYieldDesc{stocks},
byMarketCapDesc { stocks }, byMarketCapDesc{stocks},
} }
} }
@ -153,7 +210,7 @@ func c(str string) float32 {
func m(str string) float32 { func m(str string) float32 {
multiplier := 1.0 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`: case `B`:
multiplier = 1000000000.0 multiplier = 1000000000.0
case `M`: case `M`:
@ -162,7 +219,7 @@ func m(str string) float32 {
multiplier = 1000.0 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) value, _ := strconv.ParseFloat(trimmed, 32)
return float32(value * multiplier) return float32(value * multiplier)

@ -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 // Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file. // 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 // Stock stores quote information for the particular stock ticker. The data
// for all the fields except 'Advancing' is fetched using Yahoo market API. // for all the fields except 'Advancing' is fetched using Yahoo market API.
type Stock struct { type Stock struct {
Ticker string // Stock ticker. Ticker string // Stock ticker.
LastTrade string // l1: last trade. LastTrade string // l1: last trade.
Change string // c6: change real time. Change string // c6: change real time.
ChangePct string // k2: percent change real time. ChangePct string // k2: percent change real time.
Open string // o: market open price. Open string // o: market open price.
Low string // g: day's low. Low string // g: day's low.
High string // h: day's high. High string // h: day's high.
Low52 string // j: 52-weeks low. Low52 string // j: 52-weeks low.
High52 string // k: 52-weeks high. High52 string // k: 52-weeks high.
Volume string // v: volume. Volume string // v: volume.
AvgVolume string // a2: average volume. AvgVolume string // a2: average volume.
PeRatio string // r2: P/E ration real time. PeRatio string // r2: P/E ration real time.
PeRatioX string // r: P/E ration (fallback when real time is N/A). PeRatioX string // r: P/E ration (fallback when real time is N/A).
Dividend string // d: dividend. Dividend string // d: dividend.
Yield string // y: dividend yield. Yield string // y: dividend yield.
MarketCap string // j3: market cap real time. MarketCap string // j3: market cap real time.
MarketCapX string // j1: market cap (fallback when real time is N/A). MarketCapX string // j1: market cap (fallback when real time is N/A).
Advancing bool // True when change is >= $0. Advancing bool // True when change is >= $0.
} }
// Quotes stores relevant pointers as well as the array of stock quotes for // Quotes stores relevant pointers as well as the array of stock quotes for
// the tickers we are tracking. // the tickers we are tracking.
type Quotes struct { type Quotes struct {
market *Market // Pointer to Market. market *Market // Pointer to Market.
profile *Profile // Pointer to Profile. profile *Profile // Pointer to Profile.
stocks []Stock // Array of stock quote data. stocks []Stock // Array of stock quote data.
errors string // Error string if any. errors string // Error string if any.
} }
// Sets the initial values and returns new Quotes struct. // Sets the initial values and returns new Quotes struct.
func NewQuotes(market *Market, profile *Profile) *Quotes { func NewQuotes(market *Market, profile *Profile) *Quotes {
return &Quotes{ return &Quotes{
market: market, market: market,
profile: profile, profile: profile,
errors: ``, errors: ``,
} }
} }
@ -102,7 +102,7 @@ func (quotes *Quotes) Ok() (bool, string) {
// when user adds new stock tickers. // when user adds new stock tickers.
func (quotes *Quotes) AddTickers(tickers []string) (added int, err error) { func (quotes *Quotes) AddTickers(tickers []string) (added int, err error) {
if added, err = quotes.profile.AddTickers(tickers); err == nil && added > 0 { if added, err = quotes.profile.AddTickers(tickers); err == nil && added > 0 {
quotes.stocks = nil // Force fetch. quotes.stocks = nil // Force fetch.
} }
return return
} }
@ -112,7 +112,7 @@ func (quotes *Quotes) AddTickers(tickers []string) (added int, err error) {
// when user removes existing stock tickers. // when user removes existing stock tickers.
func (quotes *Quotes) RemoveTickers(tickers []string) (removed int, err error) { func (quotes *Quotes) RemoveTickers(tickers []string) (removed int, err error) {
if removed, err = quotes.profile.RemoveTickers(tickers); err == nil && removed > 0 { if removed, err = quotes.profile.RemoveTickers(tickers); err == nil && removed > 0 {
quotes.stocks = nil // Force fetch. quotes.stocks = nil // Force fetch.
} }
return return
} }

Loading…
Cancel
Save