diff --git a/profile.go b/profile.go index 0f67623..6ad77f4 100644 --- a/profile.go +++ b/profile.go @@ -1,117 +1,130 @@ // Copyright (c) 2013 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. +// Use of this source code is governed by a MIT-style license that can +// be found in the LICENSE file. package mop import ( - `sort` `encoding/json` `io/ioutil` `os/user` - `strings` + `sort` ) +// File name in user's home directory where we store the settings. const moprc = `/.moprc` +// Profile manages Mop program settings as defined by user (ex. list of +// stock tickers). The settings are serialized using JSON and saved in +// the ~/.moprc file. type Profile struct { - MarketRefresh int - QuotesRefresh int - Grouped bool - Tickers []string - SortColumn int - Ascending bool - selectedColumn int + 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. } -//----------------------------------------------------------------------------- -func (self *Profile) Initialize() *Profile { - data, err := ioutil.ReadFile(self.default_file_name()) - if err != nil { - // Set default values. - self.MarketRefresh = 12 - self.QuotesRefresh = 5 - self.Grouped = false - self.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} - self.SortColumn = 0 - self.Ascending = true - self.Save() +// Initialize attempts to load the settings from ~/.moprc file. If the +// file is not there it gets created with the default values. +func (profile *Profile) Initialize() *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. + profile.Save() } else { - json.Unmarshal(data, self) + json.Unmarshal(data, profile) } - self.selectedColumn = -1 + profile.selectedColumn = -1 - return self + return profile } -//----------------------------------------------------------------------------- -func (self *Profile) Save() error { - if data, err := json.Marshal(self); err != nil { +// Save serializes settings using JSON and saves them in ~/.moprc file. +func (profile *Profile) Save() error { + data, err := json.Marshal(profile) + if err != nil { return err - } else { - return ioutil.WriteFile(self.default_file_name(), data, 0644) } -} -//----------------------------------------------------------------------------- -func (self *Profile) ListOfTickers() string { - return strings.Join(self.Tickers, `+`) + return ioutil.WriteFile(profile.defaultFileName(), data, 0644) } -//----------------------------------------------------------------------------- -func (self *Profile) AddTickers(tickers []string) (added int, err error) { - added = 0 +// AddTickers updates the list of existing tikers to add the new ones making +// sure there are no duplicates. +func (profile *Profile) AddTickers(tickers []string) (added int, err error) { + added, err = 0, nil existing := make(map[string]bool) - for _, ticker := range self.Tickers { + // Build a hash of existing tickers so we could look it up quickly. + for _, ticker := range profile.Tickers { existing[ticker] = true } + // Iterate over the list of new tickers excluding the ones that + // already exist. for _, ticker := range tickers { if _, found := existing[ticker]; !found { - self.Tickers = append(self.Tickers, ticker) + profile.Tickers = append(profile.Tickers, ticker) added++ } } - sort.Strings(self.Tickers) - err = self.Save() + + if added > 0 { + sort.Strings(profile.Tickers) + err = profile.Save() + } + return } -//----------------------------------------------------------------------------- -func (self *Profile) RemoveTickers(tickers []string) (removed int, err error) { - removed = 0 +// RemoveTickers removes requested stock tickers from the list we track. +func (profile *Profile) RemoveTickers(tickers []string) (removed int, err error) { + removed, err = 0, nil for _, ticker := range tickers { - for i, existing := range self.Tickers { - if ticker == existing { // Requested ticker is there: remove i-th slice item. - self.Tickers = append(self.Tickers[:i], self.Tickers[i+1:]...) + for i, existing := range profile.Tickers { + if ticker == existing { + // Requested ticker is there: remove i-th slice item. + profile.Tickers = append(profile.Tickers[:i], profile.Tickers[i+1:]...) removed++ } } } - err = self.Save() + + if removed > 0 { + err = profile.Save() + } + return } -//----------------------------------------------------------------------------- -func (self *Profile) Reorder() error { - if self.selectedColumn == self.SortColumn { - self.Ascending = !self.Ascending +// Reorder gets called by the column editor to either reverse sorting order +// 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. } else { - self.SortColumn = self.selectedColumn + profile.SortColumn = profile.selectedColumn // Pick new sort column. } - return self.Save() + return profile.Save() } -//----------------------------------------------------------------------------- -func (self *Profile) Regroup() error { - self.Grouped = !self.Grouped - return self.Save() +// Regroup flips the flag that controls whether the stock quotes are grouped +// by advancing/declining issues. +func (profile *Profile) Regroup() error { + profile.Grouped = !profile.Grouped + return profile.Save() } -// private //----------------------------------------------------------------------------- -func (self *Profile) default_file_name() string { +func (profile *Profile) defaultFileName() string { usr, err := user.Current() if err != nil { panic(err) diff --git a/yahoo_quotes.go b/yahoo_quotes.go index c6b669d..94ce114 100644 --- a/yahoo_quotes.go +++ b/yahoo_quotes.go @@ -11,6 +11,7 @@ import ( `io/ioutil` `net/http` `reflect` + `strings` ) // See http://www.gummy-stuff.org/Yahoo-stocks.htm @@ -88,7 +89,7 @@ func (self *Quotes) Fetch() (this *Quotes) { } }() - url := fmt.Sprintf(quotes_url, self.profile.ListOfTickers()) + url := fmt.Sprintf(quotes_url, strings.Join(self.profile.Tickers, `+`)) response, err := http.Get(url) if err != nil { panic(err)