diff --git a/layout.go b/layout.go index 43f1519..d167e01 100644 --- a/layout.go +++ b/layout.go @@ -53,26 +53,24 @@ func (self *Layout) Market(market *Market) string { return err } + buffer := new(bytes.Buffer) markup := `{{.Dow.name}}: {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}}, {{.Sp500.name}}: {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}}, {{.Nasdaq.name}}: {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}} {{.Advances.name}}: {{.Advances.nyse}} ({{.Advances.nysep}}) on NYSE and {{.Advances.nasdaq}} ({{.Advances.nasdaqp}}) on Nasdaq. {{.Declines.name}}: {{.Declines.nyse}} ({{.Declines.nysep}}) on NYSE and {{.Declines.nasdaq}} ({{.Declines.nasdaqp}}) on Nasdaq {{if .IsClosed}}U.S. markets closed{{end}} New highs: {{.Highs.nyse}} on NYSE and {{.Highs.nasdaq}} on Nasdaq. New lows: {{.Lows.nyse}} on NYSE and {{.Lows.nasdaq}} on Nasdaq.` - template, err := template.New(`market`).Parse(markup) - if err != nil { - panic(err) - } - - buffer := new(bytes.Buffer) highlight(market.Dow, market.Sp500, market.Nasdaq) - if err := template.Execute(buffer, market); err != nil { - panic(err) - } + template,_ := template.New(`market`).Parse(markup) + template.Execute(buffer, market) return buffer.String() } //----------------------------------------------------------------------------- func (self *Layout) Quotes(quotes *Quotes) string { + if ok, err := quotes.Ok(); !ok { + return err + } + vars := struct { Now string Header string @@ -83,6 +81,7 @@ func (self *Layout) Quotes(quotes *Quotes) string { self.prettify(quotes), } + buffer := new(bytes.Buffer) markup := `{{.Now}} @@ -90,17 +89,9 @@ func (self *Layout) Quotes(quotes *Quotes) string { {{.Header}} {{range.Stocks}}{{if .Advancing}}{{end}}{{.Ticker}}{{.LastTrade}}{{.Change}}{{.ChangePct}}{{.Open}}{{.Low}}{{.High}}{{.Low52}}{{.High52}}{{.Volume}}{{.AvgVolume}}{{.PeRatio}}{{.Dividend}}{{.Yield}}{{.MarketCap}} {{end}}` - //markup += fmt.Sprintf("[%v]", quotes.profile.Grouped) - template, err := template.New(`quotes`).Parse(markup) - if err != nil { - panic(err) - } - buffer := new(bytes.Buffer) - err = template.Execute(buffer, vars) - if err != nil { - panic(err) - } + template,_ := template.New(`quotes`).Parse(markup) + template.Execute(buffer, vars) return buffer.String() } diff --git a/yahoo_market.go b/yahoo_market.go index c3c96e2..8cb9e5f 100644 --- a/yahoo_market.go +++ b/yahoo_market.go @@ -1,6 +1,7 @@ // 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. +//----------------------------------------------------------------------------- package mop @@ -13,7 +14,7 @@ import ( `strings` ) -const url = `http://finance.yahoo.com/marketupdate/overview` +const market_url = `http://finance.yahoo.com/marketupdate/overview` type Market struct { IsClosed bool @@ -67,14 +68,14 @@ func (self *Market) Initialize() *Market { //----------------------------------------------------------------------------- func (self *Market) Fetch() (this *Market) { - this = self // <-- This ensures we return correct self in case of panic attack. + this = self // <-- This ensures we return correct self after recover() from panic() attack. defer func() { if err := recover(); err != nil { self.errors = fmt.Sprintf("Error fetching market data...\n%s", err) } }() - response, err := http.Get(url) + response, err := http.Get(market_url) if err != nil { panic(err) } @@ -124,7 +125,7 @@ func (self *Market) trim(body []byte) []byte { func (self *Market) extract(snippet []byte) *Market { matches := self.regex.FindAllStringSubmatch(string(snippet), -1) if len(matches) < 1 || len(matches[0]) < 37 { - panic(`Unable to parse ` + url) + panic(`Unable to parse ` + market_url) } self.Dow[`name`] = matches[0][1] diff --git a/yahoo_quotes.go b/yahoo_quotes.go index d60610f..60268c6 100644 --- a/yahoo_quotes.go +++ b/yahoo_quotes.go @@ -1,6 +1,7 @@ // 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. +//----------------------------------------------------------------------------- package mop @@ -35,7 +36,7 @@ import ( // j3: market cap rt // j1: market cap -const yahoo_quotes_url = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=,l1c6k2oghjkva2r2rdyj3j1` +const quotes_url = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=,l1c6k2oghjkva2r2rdyj3j1` type Stock struct { Ticker string @@ -59,51 +60,56 @@ type Stock struct { } type Quotes struct { - market *Market - profile *Profile - stocks []Stock + market *Market + profile *Profile + stocks []Stock + errors string } //----------------------------------------------------------------------------- func (self *Quotes) Initialize(market *Market, profile *Profile) *Quotes { self.market = market self.profile = profile + self.errors = `` return self } // Fetch the latest stock quotes and parse raw fetched data into array of // []Stock structs. -func (self *Quotes) Fetch() *Quotes { - if self.Ready() { - // Format the URL and send the request. - url := fmt.Sprintf(yahoo_quotes_url, self.profile.ListOfTickers()) +//----------------------------------------------------------------------------- +func (self *Quotes) Fetch() (this *Quotes) { + this = self // <-- This ensures we return correct self after recover() from panic() attack. + if self.is_ready() { + defer func() { + if err := recover(); err != nil { + self.errors = fmt.Sprintf("\n\n\n\nError fetching stock quotes...\n%s", err) + } + }() + + url := fmt.Sprintf(quotes_url, self.profile.ListOfTickers()) response, err := http.Get(url) if err != nil { panic(err) } - // Fetch response and get its body. defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { panic(err) } - self.parse(self.sanitize(body)) + self.parse(sanitize(body)) } return self } -// Return true if we haven't fetched the quotes yet *or* the stock market is -// still open and we might want to grab the latest quotes. In both cases we -// make sure the list of requested tickers is not empty. -func (self *Quotes) Ready() bool { - return (self.stocks == nil || !self.market.IsClosed) && len(self.profile.Tickers) > 0 +//----------------------------------------------------------------------------- +func (self *Quotes) Ok() (bool, string) { + return self.errors == ``, self.errors } - //----------------------------------------------------------------------------- func (self *Quotes) Format() string { return new(Layout).Initialize().Quotes(self) @@ -125,6 +131,17 @@ func (self *Quotes) RemoveTickers(tickers []string) (removed int, err error) { return } +// "Private" methods. + +// Return true if we haven't fetched the quotes yet *or* the stock market is +// still open and we might want to grab the latest quotes. In both cases we +// make sure the list of requested tickers is not empty. +//----------------------------------------------------------------------------- +func (self *Quotes) is_ready() bool { + return (self.stocks == nil || !self.market.IsClosed) && len(self.profile.Tickers) > 0 +} + + //----------------------------------------------------------------------------- func (self *Quotes) parse(body []byte) *Quotes { lines := bytes.Split(body, []byte{'\n'}) @@ -155,7 +172,9 @@ func (self *Quotes) parse(body []byte) *Quotes { return self } +// Utility methods. + //----------------------------------------------------------------------------- -func (self *Quotes) sanitize(body []byte) []byte { +func sanitize(body []byte) []byte { return bytes.Replace(bytes.TrimSpace(body), []byte{'"'}, []byte{}, -1) }