2
0
mirror of https://github.com/miguelmota/cointop synced 2024-11-05 00:00:14 +00:00
Former-commit-id: 19684e3207
This commit is contained in:
Miguel Mota 2018-03-30 10:20:18 -07:00
parent 65faa8b0a3
commit 75b86fcee4
6 changed files with 673 additions and 615 deletions

114
Gopkg.lock generated Normal file
View File

@ -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

58
Gopkg.toml Normal file
View File

@ -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

View File

@ -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

500
cointop.go Normal file
View File

@ -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
}

View File

@ -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
}

View File