diff --git a/README.md b/README.md index d21af36..3c096cc 100644 --- a/README.md +++ b/README.md @@ -347,12 +347,12 @@ Key|Action The first time you run cointop, it'll create a config file in: ``` -~/.cointop/config +~/.cointop/config.toml ``` You can then configure the actions you want for each key: -(default `~/.cointop/config`) +(default `~/.cointop/config.toml`) ```toml currency = "USD" @@ -527,7 +527,7 @@ Frequently asked questions: - Q: Where is the config file located? - - A: The default configuration file is located under `~/.cointop/config` + - A: The default configuration file is located under `~/.cointop/config.toml` - Q: What format is the configuration file in? @@ -721,15 +721,15 @@ Frequently asked questions: - Q: How do I set the favorites view to be the default view? - - A: In `~/.cointop/config`, set `defaultView = "favorites"` + - A: In `~/.cointop/config.toml`, set `defaultView = "favorites"` - Q: How do I set the portfolio view to be the default view? - - A: In `~/.cointop/config`, set `defaultView = "portfolio"` + - A: In `~/.cointop/config.toml`, set `defaultView = "portfolio"` - Q: How do I set the table view to be the default view? - - A: In `~/.cointop/config`, set `defaultView = "default"` + - A: In `~/.cointop/config.toml`, set `defaultView = "default"` - Q: How can use a different config file other than the default? diff --git a/cointop/cointop.go b/cointop/cointop.go index 10b66df..a7870fd 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -28,6 +28,8 @@ var ErrInvalidAPIChoice = errors.New("Invalid API choice") type Cointop struct { g *gocui.Gui apiChoice string + colorschemename string + colorscheme *ColorScheme marketbarviewname string marketbarview *gocui.View chartview *gocui.View @@ -123,7 +125,7 @@ type apiKeys struct { cmc string } -var defaultConfigPath = "~/.cointop/config" +var defaultConfigPath = "~/.cointop/config.toml" // NewCointop initializes cointop func NewCointop(config *Config) *Cointop { diff --git a/cointop/colorscheme.go b/cointop/colorscheme.go new file mode 100644 index 0000000..091ea2a --- /dev/null +++ b/cointop/colorscheme.go @@ -0,0 +1,109 @@ +package cointop + +import ( + "github.com/fatih/color" +) + +// Colors .. +type Colors map[string]interface{} + +// Cache .. +type Cache map[string]func(...interface{}) string + +// ColorScheme ... +type ColorScheme struct { + colors Colors + cache Cache +} + +// NewColorScheme ... +func NewColorScheme(colors Colors) *ColorScheme { + return &ColorScheme{ + colors: colors, + cache: make(Cache), + } +} + +// RowText ... +func (c *ColorScheme) RowText(a ...interface{}) string { + name := "row_text" + return c.color(name, a...) +} + +func (c *ColorScheme) color(name string, a ...interface{}) string { + if cached, ok := c.cache[name]; ok { + return cached(a...) + } + + var colors []color.Attribute + if v, ok := c.colors[name+"_fg"].(string); ok { + if fg, ok := toFgAttr(v); ok { + colors = append(colors, fg) + } + } + if v, ok := c.colors[name+"_bg"].(string); ok { + if bg, ok := toBgAttr(v); ok { + colors = append(colors, bg) + } + } + if v, ok := c.colors[name+"_bold"].(bool); ok { + if bold, ok := toBoldAttr(v); ok { + colors = append(colors, bold) + } + } + if v, ok := c.colors[name+"_underline"].(bool); ok { + if underline, ok := toUnderlineAttr(v); ok { + colors = append(colors, underline) + } + } + + c.cache[name] = color.New(colors...).SprintFunc() + return c.cache[name](a...) +} + +var fgColorsMap = map[string]color.Attribute{ + "black": color.FgBlack, + "blue": color.FgBlue, + "cyan": color.FgCyan, + "green": color.FgGreen, + "magenta": color.FgMagenta, + "red": color.FgRed, + "white": color.FgWhite, + "yellow": color.FgYellow, +} + +var bgColorsMap = map[string]color.Attribute{ + "black": color.BgBlack, + "blue": color.BgBlue, + "cyan": color.BgCyan, + "green": color.BgGreen, + "magenta": color.BgMagenta, + "red": color.BgRed, + "white": color.BgWhite, + "yellow": color.BgYellow, +} + +func toFgAttr(c string) (color.Attribute, bool) { + attr, ok := fgColorsMap[c] + return attr, ok +} + +func toBgAttr(c string) (color.Attribute, bool) { + attr, ok := bgColorsMap[c] + return attr, ok +} + +func toBoldAttr(v bool) (color.Attribute, bool) { + return color.Bold, v +} + +func toUnderlineAttr(v bool) (color.Attribute, bool) { + return color.Underline, v +} + +// CointopColorscheme ... +var CointopColorscheme = ` +row_text_fg = "white" +row_text_bg = "" +row_text_bold = false +` diff --git a/cointop/config.go b/cointop/config.go index b4c242d..c81bdde 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -2,6 +2,7 @@ package cointop import ( "bytes" + "fmt" "io/ioutil" "os" "strings" @@ -19,6 +20,7 @@ type config struct { DefaultView interface{} `toml:"defaultView"` CoinMarketCap map[string]interface{} `toml:"coinmarketcap"` API interface{} `toml:"api"` + ColorScheme interface{} `toml:"colorscheme"` } func (ct *Cointop) setupConfig() error { @@ -49,6 +51,9 @@ func (ct *Cointop) setupConfig() error { if err := ct.loadAPIChoiceFromConfig(); err != nil { return err } + if err := ct.loadColorSchemeFromConfig(); err != nil { + return err + } return nil } @@ -87,6 +92,14 @@ func (ct *Cointop) makeConfigDir() error { func (ct *Cointop) makeConfigFile() error { path := ct.configPath() if _, err := os.Stat(path); os.IsNotExist(err) { + // NOTE: legacy support for default path + oldConfigPath := strings.Replace(path, "cointop/config.toml", "cointop/config", 1) + if _, err := os.Stat(oldConfigPath); err == nil { + path = oldConfigPath + ct.configFilepath = oldConfigPath + return nil + } + fo, err := os.Create(path) if err != nil { return err @@ -241,6 +254,29 @@ func (ct *Cointop) loadAPIKeysFromConfig() error { return nil } +func (ct *Cointop) loadColorSchemeFromConfig() error { + if colorscheme, ok := ct.config.ColorScheme.(string); ok { + ct.colorschemename = colorscheme + } + + var colors map[string]interface{} + if ct.colorschemename == "" { + ct.colorschemename = "cointop" + if _, err := toml.Decode(CointopColorscheme, &colors); err != nil { + return err + } + } else { + path := normalizePath(fmt.Sprintf("~/.cointop/colors/%s.toml", ct.colorschemename)) + if _, err := toml.DecodeFile(path, &colors); err != nil { + return err + } + } + + ct.colorscheme = NewColorScheme(colors) + + return nil +} + func (ct *Cointop) loadAPIChoiceFromConfig() error { apiChoice, ok := ct.config.API.(string) if ok { diff --git a/cointop/table.go b/cointop/table.go index 4b510b7..f2bfa92 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -61,7 +61,7 @@ func (ct *Cointop) refreshTable() error { ct.table.AddRow( rank, namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")), - color.White(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), 5, " ")), + ct.colorscheme.RowText(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), 5, " ")), colorprice(fmt.Sprintf("%13s", humanize.Commaf(coin.Price))), color.White(fmt.Sprintf("%15s", strconv.FormatFloat(coin.Holdings, 'f', -1, 64))), colorbalance(fmt.Sprintf("%15s", humanize.Commaf(coin.Balance))), @@ -135,7 +135,7 @@ func (ct *Cointop) refreshTable() error { ct.table.AddRow( rank, namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")), - color.White(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), symbolpadding, " ")), + ct.colorscheme.RowText(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), symbolpadding, " ")), colorprice(fmt.Sprintf("%12s", humanize.Commaf(coin.Price))), color.White(fmt.Sprintf("%18s", humanize.Commaf(coin.MarketCap))), color.White(fmt.Sprintf("%15s", humanize.Commaf(coin.Volume24H))),