From 75b86fcee48acf64e7b95c7ddf89c1b9180014c6 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Fri, 30 Mar 2018 10:20:18 -0700 Subject: [PATCH] clean up Former-commit-id: 19684e3207eaf115f78566537e936004a2f34c50 --- Gopkg.lock | 114 +++++++ Gopkg.toml | 58 ++++ README.md | 2 +- cointop.go | 500 +++++++++++++++++++++++++++++++ cointop/cointop.go | 614 -------------------------------------- services/api/interface.go | 0 6 files changed, 673 insertions(+), 615 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 cointop.go delete mode 100644 cointop/cointop.go create mode 100644 services/api/interface.go diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..30e4c4a --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,114 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/anaskhan96/soup" + packages = ["."] + revision = "cd07662ec9300d16d2ece1c24b0b29ad89f65ff4" + version = "v1.1" + +[[projects]] + branch = "master" + name = "github.com/bradfitz/slice" + packages = ["."] + revision = "d9036e2120b5ddfa53f3ebccd618c4af275f47da" + +[[projects]] + branch = "master" + name = "github.com/dustin/go-humanize" + packages = ["."] + revision = "bb3d318650d48840a39aa21a027c6630e198e626" + +[[projects]] + name = "github.com/fatih/color" + packages = ["."] + revision = "507f6050b8568533fb3f5504de8e5205fa62a114" + version = "v1.6.0" + +[[projects]] + name = "github.com/gizak/termui" + packages = ["."] + revision = "24acd523c756fd9728824cdfac66aad9d8982fb7" + version = "v2.2.0" + +[[projects]] + branch = "master" + name = "github.com/jroimartin/gocui" + packages = ["."] + revision = "4f518eddb04b8f73870836b6d1941e8aa3c06637" + +[[projects]] + name = "github.com/maruel/panicparse" + packages = ["stack"] + revision = "785840568bdc7faa0dfb1cd6c643207f03271f64" + version = "v1.1.1" + +[[projects]] + name = "github.com/mattn/go-colorable" + packages = ["."] + revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" + version = "v0.0.9" + +[[projects]] + name = "github.com/mattn/go-isatty" + packages = ["."] + revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" + version = "v0.0.3" + +[[projects]] + name = "github.com/mattn/go-runewidth" + packages = ["."] + revision = "9e777a8366cce605130a531d2cd6363d07ad7317" + version = "v0.0.2" + +[[projects]] + branch = "master" + name = "github.com/miguelmota/go-coinmarketcap" + packages = ["."] + revision = "05fd2d0c45f763462f90e630da2ddc0392d2cdd3" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/go-wordwrap" + packages = ["."] + revision = "ad45545899c7b13c020ea92b2072220eefad42b8" + +[[projects]] + branch = "master" + name = "github.com/nsf/termbox-go" + packages = ["."] + revision = "e2050e41c8847748ec5288741c0b19a8cb26d084" + +[[projects]] + branch = "master" + name = "github.com/willf/pad" + packages = ["."] + revision = "b3d7806010228b1a954b4d9c4ee4cf6cb476b063" + +[[projects]] + branch = "master" + name = "go4.org" + packages = ["reflectutil"] + revision = "e6a7f04a962e05f288f348eec6de20fe93b6b292" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = [ + "html", + "html/atom" + ] + revision = "6078986fec03a1dcc236c34816c71b0e05018fda" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "378d26f46672a356c46195c28f61bdb4c0a781dd" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "ff54fa52bac3cde3f10f5c237dad28da497594584e4291356b5efd040501fe4e" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..7106cb0 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,58 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "github.com/bradfitz/slice" + +[[constraint]] + branch = "master" + name = "github.com/dustin/go-humanize" + +[[constraint]] + name = "github.com/fatih/color" + version = "1.6.0" + +[[constraint]] + name = "github.com/gizak/termui" + version = "2.2.0" + +[[constraint]] + name = "github.com/jroimartin/gocui" + branch = "master" + +[[constraint]] + branch = "master" + name = "github.com/miguelmota/go-coinmarketcap" + +[[constraint]] + branch = "master" + name = "github.com/willf/pad" + +[prune] + go-tests = true + unused-packages = true diff --git a/README.md b/README.md index 59317f8..9cbf129 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Make sure to have [golang](https://golang.org/) installed, then do: ```bash -go get -u github.com/miguelmota/cointop/cointop +go get -u github.com/miguelmota/cointop ``` ## License diff --git a/cointop.go b/cointop.go new file mode 100644 index 0000000..f778981 --- /dev/null +++ b/cointop.go @@ -0,0 +1,500 @@ +package main + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/bradfitz/slice" + humanize "github.com/dustin/go-humanize" + "github.com/fatih/color" + "github.com/gizak/termui" + "github.com/jroimartin/gocui" + "github.com/miguelmota/cointop/table" + cmc "github.com/miguelmota/go-coinmarketcap" + "github.com/willf/pad" +) + +var ( + white = color.New(color.FgWhite).SprintFunc() + green = color.New(color.FgGreen).SprintFunc() + red = color.New(color.FgRed).SprintFunc() + cyan = color.New(color.FgCyan).SprintFunc() +) + +var ( + oneMinute int64 = 60 + oneHour = oneMinute * 60 + oneDay = oneHour * 24 + oneWeek = oneDay * 7 + oneMonth = oneDay * 30 + oneYear = oneDay * 365 +) + +// Cointop cointop +type Cointop struct { + g *gocui.Gui + chartview *gocui.View + chartpoints [][]termui.Cell + headersview *gocui.View + tableview *gocui.View + table *table.Table + sortdesc bool + currentsort string + coins []*cmc.Coin +} + +func (ct *Cointop) chartPoints(maxX int, coin string) error { + chart := termui.NewLineChart() + chart.Height = 10 + chart.AxesColor = termui.ColorWhite + chart.LineColor = termui.ColorCyan + chart.Border = false + + now := time.Now() + secs := now.Unix() + start := secs - oneDay + end := secs + + _ = coin + //graphData, err := cmc.GetCoinGraphData(coin, start, end) + graphData, err := cmc.GetGlobalMarketGraphData(start, end) + if err != nil { + log.Fatal(err) + return nil + } + + var data []float64 + /* + for i := range graphData.PriceUSD { + data = append(data, graphData.PriceUSD[i][1]) + } + */ + for i := range graphData.MarketCapByAvailableSupply { + data = append(data, graphData.MarketCapByAvailableSupply[i][1]) + } + chart.Data = data + termui.Body = termui.NewGrid() + termui.Body.Width = maxX + termui.Body.AddRows( + termui.NewRow( + termui.NewCol(12, 0, chart), + ), + ) + + var points [][]termui.Cell + // calculate layout + termui.Body.Align() + w := termui.Body.Width + h := 10 + row := termui.Body.Rows[0] + b := row.Buffer() + for i := 0; i < h; i = i + 1 { + var rowpoints []termui.Cell + for j := 0; j < w; j = j + 1 { + p := b.At(j, i) + rowpoints = append(rowpoints, p) + } + points = append(points, rowpoints) + } + + ct.chartpoints = points + return nil +} + +func main() { + g, err := gocui.NewGui(gocui.Output256) + if err != nil { + log.Fatalf("new gocui: %v", err) + } + defer g.Close() + g.Cursor = true + g.Mouse = true + g.Highlight = true + + ct := Cointop{ + g: g, + } + g.SetManagerFunc(ct.layout) + + if err := ct.keybindings(g); err != nil { + log.Fatalf("keybindings: %v", err) + } + + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + log.Fatalf("main loop: %v", err) + } +} + +func (ct *Cointop) quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} + +func (ct *Cointop) keybindings(g *gocui.Gui) error { + if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, ct.cursorDown); err != nil { + return err + } + if err := g.SetKeybinding("", 'j', gocui.ModNone, ct.cursorDown); err != nil { + return err + } + if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, ct.cursorUp); err != nil { + return err + } + if err := g.SetKeybinding("", 'k', gocui.ModNone, ct.cursorUp); err != nil { + return err + } + if err := g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, ct.pageDown); err != nil { + return err + } + if err := g.SetKeybinding("", gocui.KeyCtrlU, gocui.ModNone, ct.pageUp); err != nil { + return err + } + if err := g.SetKeybinding("", 'r', gocui.ModNone, ct.sort("rank", false)); err != nil { + return err + } + if err := g.SetKeybinding("", 'n', gocui.ModNone, ct.sort("name", true)); err != nil { + return err + } + if err := g.SetKeybinding("", 's', gocui.ModNone, ct.sort("symbol", false)); err != nil { + return err + } + if err := g.SetKeybinding("", 'p', gocui.ModNone, ct.sort("price", true)); err != nil { + return err + } + if err := g.SetKeybinding("", 'm', gocui.ModNone, ct.sort("marketcap", true)); err != nil { + return err + } + if err := g.SetKeybinding("", 'v', gocui.ModNone, ct.sort("24hvolume", true)); err != nil { + return err + } + if err := g.SetKeybinding("", '1', gocui.ModNone, ct.sort("1hchange", true)); err != nil { + return err + } + if err := g.SetKeybinding("", '2', gocui.ModNone, ct.sort("24hchange", true)); err != nil { + return err + } + if err := g.SetKeybinding("", '7', gocui.ModNone, ct.sort("7dchange", true)); err != nil { + return err + } + if err := g.SetKeybinding("", 't', gocui.ModNone, ct.sort("totalsupply", true)); err != nil { + return err + } + if err := g.SetKeybinding("", 'a', gocui.ModNone, ct.sort("availablesupply", true)); err != nil { + return err + } + if err := g.SetKeybinding("", 'l', gocui.ModNone, ct.sort("lastupdated", true)); err != nil { + return err + } + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, ct.quit); err != nil { + return err + } + if err := g.SetKeybinding("", 'q', gocui.ModNone, ct.quit); err != nil { + return err + } + if err := g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, ct.quit); err != nil { + return err + } + return nil +} + +func (ct *Cointop) sort(sortby string, desc bool) func(g *gocui.Gui, v *gocui.View) error { + return func(g *gocui.Gui, v *gocui.View) error { + if ct.currentsort == sortby { + ct.sortdesc = !ct.sortdesc + } else { + ct.currentsort = sortby + ct.sortdesc = desc + } + slice.Sort(ct.coins[:], func(i, j int) bool { + if ct.sortdesc { + i, j = j, i + } + a := ct.coins[i] + b := ct.coins[j] + switch sortby { + case "rank": + return a.Rank < b.Rank + case "name": + return a.Name < b.Name + case "symbol": + return a.Symbol < b.Symbol + case "price": + return a.PriceUSD < b.PriceUSD + case "marketcap": + return a.MarketCapUSD < b.MarketCapUSD + case "24hvolume": + return a.USD24HVolume < b.USD24HVolume + case "1hchange": + return a.PercentChange1H < b.PercentChange1H + case "24hchange": + return a.PercentChange24H < b.PercentChange24H + case "7dchange": + return a.PercentChange7D < b.PercentChange7D + case "totalsupply": + return a.TotalSupply < b.TotalSupply + case "availablesupply": + return a.AvailableSupply < b.AvailableSupply + case "lastupdated": + return a.LastUpdated < b.LastUpdated + default: + return a.Rank < b.Rank + } + }) + g.Update(func(g *gocui.Gui) error { + ct.tableview.Clear() + ct.setTable() + return nil + }) + g.Update(func(g *gocui.Gui) error { + ct.chartview.Clear() + maxX, _ := g.Size() + _, cy := ct.chartview.Cursor() + coin := "ethereum" + ct.chartPoints(maxX, coin) + ct.setChart() + fmt.Fprint(v, cy) + return nil + }) + + return nil + } +} + +func (ct *Cointop) cursorDown(g *gocui.Gui, v *gocui.View) error { + if ct.tableview == nil { + return nil + } + _, y := ct.tableview.Origin() + cx, cy := ct.tableview.Cursor() + numRows := len(ct.coins) - 1 + //fmt.Fprint(v, cy) + if (cy + y + 1) > numRows { + return nil + } + if err := ct.tableview.SetCursor(cx, cy+1); err != nil { + ox, oy := ct.tableview.Origin() + if err := ct.tableview.SetOrigin(ox, oy+1); err != nil { + return err + } + } + return nil +} + +func (ct *Cointop) cursorUp(g *gocui.Gui, v *gocui.View) error { + if ct.tableview == nil { + return nil + } + ox, oy := ct.tableview.Origin() + cx, cy := ct.tableview.Cursor() + //fmt.Fprint(v, oy) + if err := ct.tableview.SetCursor(cx, cy-1); err != nil && oy > 0 { + if err := ct.tableview.SetOrigin(ox, oy-1); err != nil { + return err + } + } + return nil +} + +func (ct *Cointop) pageDown(g *gocui.Gui, v *gocui.View) error { + if ct.tableview == nil { + return nil + } + _, y := ct.tableview.Origin() + cx, cy := ct.tableview.Cursor() + numRows := len(ct.coins) - 1 + _, sy := ct.tableview.Size() + rows := sy + if (cy + y + rows) > numRows { + // go to last row + ct.tableview.SetCursor(cx, numRows) + ox, _ := ct.tableview.Origin() + ct.tableview.SetOrigin(ox, numRows) + return nil + } + if err := ct.tableview.SetCursor(cx, cy+rows); err != nil { + ox, oy := ct.tableview.Origin() + if err := ct.tableview.SetOrigin(ox, oy+rows); err != nil { + return err + } + } + return nil +} + +func (ct *Cointop) pageUp(g *gocui.Gui, v *gocui.View) error { + if ct.tableview == nil { + return nil + } + ox, oy := v.Origin() + cx, cy := v.Cursor() + _, sy := v.Size() + rows := sy + //fmt.Fprint(v, oy) + if err := v.SetCursor(cx, cy-rows); err != nil && oy > 0 { + if err := v.SetOrigin(ox, oy-rows); err != nil { + return err + } + } + return nil +} + +func fetchData() ([]*cmc.Coin, error) { + limit := 100 + result := []*cmc.Coin{} + coins, err := cmc.GetAllCoinData(int(limit)) + if err != nil { + return result, err + } + + for i := range coins { + coin := coins[i] + result = append(result, &coin) + } + + return result, nil +} + +func (ct *Cointop) layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + + chartHeight := 10 + if v, err := g.SetView("chart", 0, 0, maxX, chartHeight); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Frame = false + ct.chartview = v + ct.setChart() + } + + if v, err := g.SetView("header", 0, chartHeight, maxX, maxY); err != nil { + if err != gocui.ErrUnknownView { + return err + } + t := table.New().SetWidth(maxX) + + headers := []string{ + pad.Right("[r]ank", 13, " "), + pad.Right("[n]ame", 13, " "), + pad.Right("[s]ymbol", 8, " "), + pad.Left("[p]rice", 10, " "), + pad.Left("[m]arket cap", 17, " "), + pad.Left("24H [v]olume", 15, " "), + pad.Left("[1]H%", 9, " "), + pad.Left("[2]4H%", 9, " "), + pad.Left("[7]D%", 9, " "), + pad.Left("[t]otal supply", 20, " "), + pad.Left("[a]vailable supply", 19, " "), + pad.Left("[l]ast updated", 17, " "), + } + for _, h := range headers { + t.AddCol(h) + } + + t.Format().Fprint(v) + v.Highlight = true + v.SelBgColor = gocui.ColorGreen + v.SelFgColor = gocui.ColorBlack + v.Frame = false + } + + if v, err := g.SetView("table", 0, chartHeight+1, maxX, maxY); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Highlight = true + v.SelBgColor = gocui.ColorCyan + v.SelFgColor = gocui.ColorBlack + v.Frame = false + ct.tableview = v + ct.setTable() + } + + return nil +} + +func (ct *Cointop) setChart() error { + maxX, _ := ct.g.Size() + if len(ct.chartpoints) == 0 { + ct.chartPoints(maxX, "bitcoin") + } + + for i := range ct.chartpoints { + var s string + for j := range ct.chartpoints[i] { + p := ct.chartpoints[i][j] + s = fmt.Sprintf("%s%c", s, p.Ch) + } + fmt.Fprintln(ct.chartview, s) + } + return nil +} + +func (ct *Cointop) setTable() error { + maxX, _ := ct.g.Size() + ct.table = table.New().SetWidth(maxX) + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.AddCol("") + ct.table.HideColumHeaders = true + var err error + if len(ct.coins) == 0 { + ct.coins, err = fetchData() + if err != nil { + return err + } + } + for _, coin := range ct.coins { + unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64) + lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02") + colorprice := cyan + color1h := white + color24h := white + color7d := white + if coin.PercentChange1H > 0 { + color1h = green + } + if coin.PercentChange1H < 0 { + color1h = red + } + if coin.PercentChange24H > 0 { + color24h = green + } + if coin.PercentChange24H < 0 { + color24h = red + } + if coin.PercentChange7D > 0 { + color7d = green + } + if coin.PercentChange7D < 0 { + color7d = red + } + ct.table.AddRow( + pad.Left(fmt.Sprint(coin.Rank), 4, " "), + pad.Right(coin.Name, 22, " "), + pad.Right(coin.Symbol, 6, " "), + colorprice(pad.Left(humanize.Commaf(coin.PriceUSD), 12, " ")), + pad.Left(humanize.Commaf(coin.MarketCapUSD), 17, " "), + pad.Left(humanize.Commaf(coin.USD24HVolume), 15, " "), + color1h(pad.Left(fmt.Sprintf("%.2f%%", coin.PercentChange1H), 9, " ")), + color24h(pad.Left(fmt.Sprintf("%.2f%%", coin.PercentChange24H), 9, " ")), + color7d(pad.Left(fmt.Sprintf("%.2f%%", coin.PercentChange7D), 9, " ")), + pad.Left(humanize.Commaf(coin.TotalSupply), 20, " "), + pad.Left(humanize.Commaf(coin.AvailableSupply), 18, " "), + pad.Left(fmt.Sprintf("%s", lastUpdated), 18, " "), + // add %percent of cap + ) + } + + ct.table.Format().Fprint(ct.tableview) + return nil +} diff --git a/cointop/cointop.go b/cointop/cointop.go deleted file mode 100644 index 4860595..0000000 --- a/cointop/cointop.go +++ /dev/null @@ -1,614 +0,0 @@ -package main - -import ( - "fmt" - "log" - "math" - "strconv" - "time" - - "github.com/bradfitz/slice" - humanize "github.com/dustin/go-humanize" - "github.com/fatih/color" - "github.com/gizak/termui" - "github.com/jroimartin/gocui" - "github.com/miguelmota/cointop/table" - cmc "github.com/miguelmota/go-coinmarketcap" - "github.com/willf/pad" -) - -var white = color.New(color.FgWhite).SprintFunc() -var cyan = color.New(color.FgCyan).SprintFunc() -var red = color.New(color.FgRed).SprintFunc() -var green = color.New(color.FgGreen).SprintFunc() - -func fmtTime(v interface{}) string { - t := v.(time.Time) - return t.Format("2006-01-02 15:04:05") -} - -func ltTime(a interface{}, b interface{}) bool { - return a.(time.Time).Before(b.(time.Time)) -} - -func round(num float64) int { - return int(num + math.Copysign(0.5, num)) -} - -func toFixed(num float64, precision int) float64 { - output := math.Pow(10, float64(precision)) - return float64(round(num*output)) / output -} - -func chartCells(maxX int, coin string) [][]termui.Cell { - //_ = termui.Init() - //defer termui.Close() - - // quit on Ctrl-c - /* - termui.Handle("/sys/kbd/C-c", func(termui.Event) { - termui.StopLoop() - }) - */ - chart := termui.NewLineChart() - //chart.DataLabels = []string{""} - chart.Height = 10 - chart.AxesColor = termui.ColorWhite - chart.LineColor = termui.ColorCyan //| termui.AttrBold - chart.Border = false - - var ( - oneMinute int64 = 60 - oneHour = oneMinute * 60 - oneDay = oneHour * 24 - //oneWeek = oneDay * 7 - //oneMonth = oneDay * 30 - //oneYear = oneDay * 365 - ) - - now := time.Now() - secs := now.Unix() - start := secs - oneDay - end := secs - - _ = coin - //graphData, err := cmc.GetCoinGraphData(coin, start, end) - graphData, err := cmc.GetGlobalMarketGraphData(start, end) - if err != nil { - log.Fatal(err) - } - - var data []float64 - /* - for i := range graphData.PriceUSD { - data = append(data, graphData.PriceUSD[i][1]) - } - */ - for i := range graphData.MarketCapByAvailableSupply { - data = append(data, graphData.MarketCapByAvailableSupply[i][1]) - } - chart.Data = data - //h := chart.InnerHeight() - //w := chart.InnerWidth() - - // add grid rows and columns - termui.Body = termui.NewGrid() - //termui.Body.Rows = termui.Body.Rows[:0] - termui.Body.Width = maxX - termui.Body.AddRows( - termui.NewRow( - termui.NewCol(12, 0, chart), - ), - ) - - var points [][]termui.Cell - - // calculate layout - termui.Body.Align() - // render to terminal - //termui.Render(termui.Body) - w := termui.Body.Width - h := 10 - row := termui.Body.Rows[0] - b := row.Buffer() - for i := 0; i < h; i = i + 1 { - var rowpoints []termui.Cell - for j := 0; j < w; j = j + 1 { - _ = b - p := b.At(j, i) - c := p - rowpoints = append(rowpoints, c) - } - points = append(points, rowpoints) - } - //termui.Loop() - - return points -} - -var points [][]termui.Cell - -func main() { - g, err := gocui.NewGui(gocui.Output256) - if err != nil { - log.Fatalf("new gocui: %v", err) - } - defer g.Close() - - g.Cursor = true - g.Mouse = true - g.Highlight = true - g.SetManagerFunc(layout) - - if err := keybindings(g); err != nil { - log.Fatalf("keybindings: %v", err) - } - - if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { - log.Fatalf("main loop: %v", err) - } -} - -func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit -} - -func keybindings(g *gocui.Gui) error { - if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil { - return err - } - - if err := g.SetKeybinding("", 'j', gocui.ModNone, cursorDown); err != nil { - return err - } - - if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil { - return err - } - - if err := g.SetKeybinding("", 'k', gocui.ModNone, cursorUp); err != nil { - return err - } - - if err := g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, pageDown); err != nil { - return err - } - - if err := g.SetKeybinding("", gocui.KeyCtrlU, gocui.ModNone, pageUp); err != nil { - return err - } - - if err := g.SetKeybinding("", 'r', gocui.ModNone, sort("rank", false)); err != nil { - return err - } - if err := g.SetKeybinding("", 'n', gocui.ModNone, sort("name", true)); err != nil { - return err - } - if err := g.SetKeybinding("", 's', gocui.ModNone, sort("symbol", false)); err != nil { - return err - } - if err := g.SetKeybinding("", 'p', gocui.ModNone, sort("price", true)); err != nil { - return err - } - if err := g.SetKeybinding("", 'm', gocui.ModNone, sort("marketcap", true)); err != nil { - return err - } - if err := g.SetKeybinding("", 'v', gocui.ModNone, sort("24hvolume", true)); err != nil { - return err - } - if err := g.SetKeybinding("", '1', gocui.ModNone, sort("1hchange", true)); err != nil { - return err - } - if err := g.SetKeybinding("", '2', gocui.ModNone, sort("24hchange", true)); err != nil { - return err - } - if err := g.SetKeybinding("", '7', gocui.ModNone, sort("7dchange", true)); err != nil { - return err - } - if err := g.SetKeybinding("", 't', gocui.ModNone, sort("totalsupply", true)); err != nil { - return err - } - if err := g.SetKeybinding("", 'a', gocui.ModNone, sort("availablesupply", true)); err != nil { - return err - } - if err := g.SetKeybinding("", 'l', gocui.ModNone, sort("lastupdated", true)); err != nil { - return err - } - - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - return err - } - if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil { - return err - } - if err := g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, quit); err != nil { - return err - } - - return nil -} - -type strategy func(g *gocui.Gui, v *gocui.View) error - -var sortDesc bool -var currentsort string - -func sort(sortBy string, desc bool) strategy { - return func(g *gocui.Gui, v *gocui.View) error { - - if currentsort == sortBy { - sortDesc = !sortDesc - } else { - currentsort = sortBy - sortDesc = desc - } - - slice.Sort(coins[:], func(i, j int) bool { - if sortDesc == true { - i, j = j, i - } - a := coins[i] - b := coins[j] - switch sortBy { - case "rank": - return a.Rank < b.Rank - case "name": - return a.Name < b.Name - case "symbol": - return a.Symbol < b.Symbol - case "price": - return a.PriceUSD < b.PriceUSD - case "marketcap": - return a.MarketCapUSD < b.MarketCapUSD - case "24hvolume": - return a.USD24HVolume < b.USD24HVolume - case "1hchange": - return a.PercentChange1H < b.PercentChange1H - case "24hchange": - return a.PercentChange24H < b.PercentChange24H - case "7dchange": - return a.PercentChange7D < b.PercentChange7D - case "totalsupply": - return a.TotalSupply < b.TotalSupply - case "availablesupply": - return a.AvailableSupply < b.AvailableSupply - case "lastupdated": - return a.LastUpdated < b.LastUpdated - default: - return a.Rank < b.Rank - } - }) - - g.Update(func(g *gocui.Gui) error { - v, _ = g.View("table") - v.Clear() - setTable(g) - return nil - }) - - g.Update(func(g *gocui.Gui) error { - v, _ = g.View("chart") - v.Clear() - - maxX, _ := g.Size() - _, cy := v.Cursor() - coin := "ethereum" - points = chartCells(maxX, coin) - setChart(g, v) - fmt.Fprint(v, cy) - return nil - }) - - return nil - } -} - -var coins []*cmc.Coin - -func cursorDown(g *gocui.Gui, v *gocui.View) error { - v, _ = g.View("table") - if v != nil { - _, y := v.Origin() - cx, cy := v.Cursor() - numRows := len(coins) - 1 - //fmt.Fprint(v, cy) - if (cy + y + 1) > numRows { - return nil - } - if err := v.SetCursor(cx, cy+1); err != nil { - ox, oy := v.Origin() - if err := v.SetOrigin(ox, oy+1); err != nil { - return err - } - } - } - return nil -} - -func cursorUp(g *gocui.Gui, v *gocui.View) error { - v, _ = g.View("table") - if v != nil { - ox, oy := v.Origin() - cx, cy := v.Cursor() - //fmt.Fprint(v, oy) - if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 { - if err := v.SetOrigin(ox, oy-1); err != nil { - return err - } - } - } - return nil -} - -func pageDown(g *gocui.Gui, v *gocui.View) error { - v, _ = g.View("table") - if v != nil { - _, y := v.Origin() - cx, cy := v.Cursor() - numRows := len(coins) - 1 - _, sy := v.Size() - rows := sy - if (cy + y + rows) > numRows { - // go to last row - v.SetCursor(cx, numRows) - ox, _ := v.Origin() - v.SetOrigin(ox, numRows) - return nil - } - if err := v.SetCursor(cx, cy+rows); err != nil { - ox, oy := v.Origin() - if err := v.SetOrigin(ox, oy+rows); err != nil { - return err - } - } - } - return nil -} - -func pageUp(g *gocui.Gui, v *gocui.View) error { - v, _ = g.View("table") - if v != nil { - ox, oy := v.Origin() - cx, cy := v.Cursor() - _, sy := v.Size() - rows := sy - //fmt.Fprint(v, oy) - if err := v.SetCursor(cx, cy-rows); err != nil && oy > 0 { - if err := v.SetOrigin(ox, oy-rows); err != nil { - return err - } - } - } - return nil -} - -func fetchData() ([]*cmc.Coin, error) { - limit := 100 - result := []*cmc.Coin{} - coins, err := cmc.GetAllCoinData(int(limit)) - if err != nil { - return result, err - } - - for i := range coins { - coin := coins[i] - result = append(result, &coin) - } - - return result, nil -} - -func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - - chartHeight := 10 - if v, err := g.SetView("chart", 0, 0, maxX, chartHeight); err != nil { - if err != gocui.ErrUnknownView { - return err - } - //v.Frame = false - setChart(g, v) - } - - if v, err := g.SetView("header", 0, chartHeight, maxX, maxY); err != nil { - if err != gocui.ErrUnknownView { - return err - } - t := table.New().SetWidth(maxX) - - headers := []string{ - pad.Right("[r]ank", 13, " "), - pad.Right("[n]ame", 13, " "), - pad.Right("[s]ymbol", 8, " "), - pad.Left("[p]rice", 10, " "), - pad.Left("[m]arket cap", 17, " "), - pad.Left("24H [v]olume", 15, " "), - pad.Left("[1]H%", 9, " "), - pad.Left("[2]4H%", 9, " "), - pad.Left("[7]D%", 9, " "), - pad.Left("[t]otal supply", 20, " "), - pad.Left("[a]vailable supply", 19, " "), - pad.Left("[l]ast updated", 17, " "), - } - for _, h := range headers { - t.AddCol(h) - } - - t.Format().Fprint(v) - v.Highlight = true - v.SelBgColor = gocui.ColorGreen - v.SelFgColor = gocui.ColorBlack - v.Frame = false - } - - if v, err := g.SetView("table", 0, chartHeight+1, maxX, maxY); err != nil { - if err != gocui.ErrUnknownView { - return err - } - _ = v - _ = maxY - setTable(g) - } - return nil -} - -func setChart(g *gocui.Gui, v *gocui.View) error { - v.Frame = false - /* - tm.Clear() - tm.MoveCursor(0, 0) - chart := tm.NewLineChart(maxX/2, 9) - //chart.Flags = tm.DRAW_INDEPENDENT - chart.Flags = tm.DRAW_RELATIVE - data := new(tm.DataTable) - data.AddColumn("") - data.AddColumn("") - - var ( - oneMinute int64 = 60 - oneHour = oneMinute * 60 - oneDay = oneHour * 24 - //oneWeek = oneDay * 7 - oneMonth = oneDay * 30 - //oneYear = oneDay * 365 - ) - - now := time.Now() - secs := now.Unix() - //start := secs - oneDay - start := secs - oneMonth - end := secs - - coin := "ethereum" - graphData, err := cmc.GetCoinGraphData(coin, start, end) - if err != nil { - log.Fatal(err) - } - - var dt []float64 - for i := range graphData.PriceUSD { - dt = append(dt, graphData.PriceUSD[i][1]) - } - - for i, d := range dt { - _ = d - _ = i - data.AddRow(float64(i), d) - } - _ = dt - - out := chart.Draw(data, []int{0, 6}) - fmt.Fprint(v, out) - //tm.Println(out) - var buf bytes.Buffer - tm.Output = bufio.NewWriter(&buf) - //tm.Output = bufio.NewWriter(v) - _ = buf - tm.Flush() - _ = v - //buf.WriteTo(v) - //buf.WriteTo(os.Stdout) - */ - - maxX, _ := g.Size() - if len(points) == 0 { - points = chartCells(maxX, "bitcoin") - } - - for i := range points { - var s string - for j := range points[i] { - p := points[i][j] - s = fmt.Sprintf("%s%c", s, p.Ch) - } - fmt.Fprintln(v, s) - } - return nil -} - -func setTable(g *gocui.Gui) error { - maxX, _ := g.Size() - v, _ := g.View("table") - t := table.New().SetWidth(maxX) - - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.AddCol("") - t.HideColumHeaders = true - - var err error - if len(coins) == 0 { - coins, err = fetchData() - if err != nil { - return err - } - } - for _, coin := range coins { - unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64) - lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02") - colorprice := cyan - color1h := white - color24h := white - color7d := white - if coin.PercentChange1H > 0 { - color1h = green - } - if coin.PercentChange1H < 0 { - color1h = red - } - if coin.PercentChange24H > 0 { - color24h = green - } - if coin.PercentChange24H < 0 { - color24h = red - } - if coin.PercentChange7D > 0 { - color7d = green - } - if coin.PercentChange7D < 0 { - color7d = red - } - _ = color7d - t.AddRow( - pad.Left(fmt.Sprint(coin.Rank), 4, " "), - pad.Right(coin.Name, 22, " "), - pad.Right(coin.Symbol, 6, " "), - colorprice(pad.Left(humanize.Commaf(coin.PriceUSD), 12, " ")), - pad.Left(humanize.Commaf(coin.MarketCapUSD), 17, " "), - pad.Left(humanize.Commaf(coin.USD24HVolume), 15, " "), - color1h(pad.Left(fmt.Sprintf("%.2f%%", coin.PercentChange1H), 9, " ")), - color24h(pad.Left(fmt.Sprintf("%.2f%%", coin.PercentChange24H), 9, " ")), - color7d(pad.Left(fmt.Sprintf("%.2f%%", coin.PercentChange7D), 9, " ")), - pad.Left(humanize.Commaf(coin.TotalSupply), 20, " "), - pad.Left(humanize.Commaf(coin.AvailableSupply), 18, " "), - pad.Left(fmt.Sprintf("%s", lastUpdated), 18, " "), - // add %percent of cap - ) - } - - //t.SortAsc("Name").SortDesc("Age").Sort().Format().Fprint(v) - t.Format().Fprint(v) - //v.Editable = true - //v.SetCursor(0, 2) - v.Highlight = true - v.SelBgColor = gocui.ColorCyan - v.SelFgColor = gocui.ColorBlack - v.Frame = false - //v.Autoscroll = true - - //buffer := ln.Buffer() - //buffer. - - // Sort by time - // t.SortAscFn("Created", ltTime).Sort().Format().Fprint(v) - return nil -} diff --git a/services/api/interface.go b/services/api/interface.go new file mode 100644 index 0000000..e69de29