mirror of
https://github.com/miguelmota/cointop
synced 2024-11-05 00:00:14 +00:00
Merge branch 'feature/locale-dates' of https://github.com/lyricnz/cointop into feature/locale-dates
This commit is contained in:
commit
1a5b4a5a09
25
.github/workflows/go.yml
vendored
Normal file
25
.github/workflows/go.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
@ -17,7 +17,7 @@ func HoldingsCmd() *cobra.Command {
|
||||
var config string
|
||||
var sortBy string
|
||||
var sortDesc bool
|
||||
var format string = "table"
|
||||
var format = "table"
|
||||
var humanReadable bool
|
||||
var filter []string
|
||||
var cols []string
|
||||
|
@ -14,15 +14,15 @@ import (
|
||||
|
||||
// ServerCmd ...
|
||||
func ServerCmd() *cobra.Command {
|
||||
var port uint = 22
|
||||
var address string = "0.0.0.0"
|
||||
var idleTimeout uint = 0
|
||||
var maxTimeout uint = 0
|
||||
var maxSessions uint = 0
|
||||
var executableBinary string = "cointop"
|
||||
var hostKeyFile string = cssh.DefaultHostKeyFile
|
||||
var userConfigType string = cssh.UserConfigTypePublicKey
|
||||
var colorsDir string = os.Getenv("COINTOP_COLORS_DIR")
|
||||
port := uint(22)
|
||||
address := "0.0.0.0"
|
||||
idleTimeout := uint(0)
|
||||
maxTimeout := uint(0)
|
||||
maxSessions := uint(0)
|
||||
executableBinary := "cointop"
|
||||
hostKeyFile := cssh.DefaultHostKeyFile
|
||||
userConfigType := cssh.UserConfigTypePublicKey
|
||||
colorsDir := os.Getenv("COINTOP_COLORS_DIR")
|
||||
|
||||
serverCmd := &cobra.Command{
|
||||
Use: "server",
|
||||
|
@ -25,8 +25,7 @@ type ChartView = ui.View
|
||||
|
||||
// NewChartView returns a new chart view
|
||||
func NewChartView() *ChartView {
|
||||
var view *ChartView = ui.NewView("chart")
|
||||
return view
|
||||
return ui.NewView("chart")
|
||||
}
|
||||
|
||||
var chartLock sync.Mutex
|
||||
@ -50,17 +49,17 @@ func ChartRanges() []string {
|
||||
// ChartRangesMap returns map of chart range time ranges
|
||||
func ChartRangesMap() map[string]time.Duration {
|
||||
return map[string]time.Duration{
|
||||
"All Time": time.Duration(10 * 365 * 24 * time.Hour),
|
||||
"YTD": time.Duration(1 * time.Second), // this will be calculated
|
||||
"1Y": time.Duration(365 * 24 * time.Hour),
|
||||
"6M": time.Duration(365 / 2 * 24 * time.Hour),
|
||||
"3M": time.Duration(365 / 4 * 24 * time.Hour),
|
||||
"1M": time.Duration(365 / 12 * 24 * time.Hour),
|
||||
"7D": time.Duration(24 * 7 * time.Hour),
|
||||
"3D": time.Duration(24 * 3 * time.Hour),
|
||||
"24H": time.Duration(24 * time.Hour),
|
||||
"6H": time.Duration(6 * time.Hour),
|
||||
"1H": time.Duration(1 * time.Hour),
|
||||
"All Time": 10 * 365 * 24 * time.Hour,
|
||||
"YTD": 1 * time.Second, // this will be calculated
|
||||
"1Y": 365 * 24 * time.Hour,
|
||||
"6M": 365 / 2 * 24 * time.Hour,
|
||||
"3M": 365 / 4 * 24 * time.Hour,
|
||||
"1M": 365 / 12 * 24 * time.Hour,
|
||||
"7D": 24 * 7 * time.Hour,
|
||||
"3D": 24 * 3 * time.Hour,
|
||||
"24H": 24 * time.Hour,
|
||||
"6H": 6 * time.Hour,
|
||||
"1H": 1 * time.Hour,
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +118,7 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error {
|
||||
|
||||
rangeseconds := ct.chartRangesMap[ct.State.selectedChartRange]
|
||||
if ct.State.selectedChartRange == "YTD" {
|
||||
ytd := time.Now().Unix() - int64(timeutil.BeginningOfYear().Unix())
|
||||
ytd := time.Now().Unix() - timeutil.BeginningOfYear().Unix()
|
||||
rangeseconds = time.Duration(ytd) * time.Second
|
||||
}
|
||||
|
||||
@ -216,7 +215,7 @@ func (ct *Cointop) PortfolioChart() error {
|
||||
selectedChartRange := ct.State.selectedChartRange // cache here
|
||||
rangeseconds := ct.chartRangesMap[selectedChartRange]
|
||||
if selectedChartRange == "YTD" {
|
||||
ytd := time.Now().Unix() - int64(timeutil.BeginningOfYear().Unix())
|
||||
ytd := time.Now().Unix() - timeutil.BeginningOfYear().Unix()
|
||||
rangeseconds = time.Duration(ytd) * time.Second
|
||||
}
|
||||
|
||||
|
@ -136,6 +136,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
||||
})
|
||||
case "24h_volume":
|
||||
text := humanize.Monetaryf(coin.Volume24H, 0)
|
||||
if ct.IsActiveTableCompactNotation() {
|
||||
text = humanize.ScaleNumericf(coin.Volume24H, 3)
|
||||
}
|
||||
ct.SetTableColumnWidthFromString(header, text)
|
||||
ct.SetTableColumnAlignLeft(header, false)
|
||||
rowCells = append(rowCells,
|
||||
@ -243,6 +246,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
||||
})
|
||||
case "market_cap":
|
||||
text := humanize.Monetaryf(coin.MarketCap, 0)
|
||||
if ct.IsActiveTableCompactNotation() {
|
||||
text = humanize.ScaleNumericf(coin.MarketCap, 3)
|
||||
}
|
||||
ct.SetTableColumnWidthFromString(header, text)
|
||||
ct.SetTableColumnAlignLeft(header, false)
|
||||
rowCells = append(rowCells,
|
||||
@ -255,6 +261,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
||||
})
|
||||
case "total_supply":
|
||||
text := humanize.Numericf(coin.TotalSupply, 0)
|
||||
if ct.IsActiveTableCompactNotation() {
|
||||
text = humanize.ScaleNumericf(coin.TotalSupply, 3)
|
||||
}
|
||||
ct.SetTableColumnWidthFromString(header, text)
|
||||
ct.SetTableColumnAlignLeft(header, false)
|
||||
rowCells = append(rowCells,
|
||||
@ -267,6 +276,9 @@ func (ct *Cointop) GetCoinsTable() *table.Table {
|
||||
})
|
||||
case "available_supply":
|
||||
text := humanize.Numericf(coin.AvailableSupply, 0)
|
||||
if ct.IsActiveTableCompactNotation() {
|
||||
text = humanize.ScaleNumericf(coin.AvailableSupply, 3)
|
||||
}
|
||||
ct.SetTableColumnWidthFromString(header, text)
|
||||
ct.SetTableColumnAlignLeft(header, false)
|
||||
rowCells = append(rowCells,
|
||||
|
@ -89,6 +89,11 @@ type State struct {
|
||||
priceAlerts *PriceAlerts
|
||||
priceAlertEditID string
|
||||
priceAlertNewID string
|
||||
|
||||
compactNotation bool
|
||||
tableCompactNotation bool
|
||||
favoritesCompactNotation bool
|
||||
portfolioCompactNotation bool
|
||||
}
|
||||
|
||||
// Cointop cointop
|
||||
@ -181,20 +186,23 @@ var DefaultCurrency = "USD"
|
||||
// DefaultChartRange ...
|
||||
var DefaultChartRange = "1Y"
|
||||
|
||||
// DefaultCompactNotation ...
|
||||
var DefaultCompactNotation = false
|
||||
|
||||
// DefaultMaxChartWidth ...
|
||||
var DefaultMaxChartWidth int = 175
|
||||
var DefaultMaxChartWidth = 175
|
||||
|
||||
// DefaultChartHeight ...
|
||||
var DefaultChartHeight int = 10
|
||||
var DefaultChartHeight = 10
|
||||
|
||||
// DefaultSortBy ...
|
||||
var DefaultSortBy = "rank"
|
||||
|
||||
// DefaultPerPage ...
|
||||
var DefaultPerPage uint = 100
|
||||
var DefaultPerPage = uint(100)
|
||||
|
||||
// DefaultMaxPages ...
|
||||
var DefaultMaxPages uint = 35
|
||||
var DefaultMaxPages = uint(35)
|
||||
|
||||
// DefaultColorscheme ...
|
||||
var DefaultColorscheme = "cointop"
|
||||
@ -291,6 +299,10 @@ func NewCointop(config *Config) (*Cointop, error) {
|
||||
Entries: make([]*PriceAlert, 0),
|
||||
SoundEnabled: true,
|
||||
},
|
||||
compactNotation: DefaultCompactNotation,
|
||||
tableCompactNotation: DefaultCompactNotation,
|
||||
favoritesCompactNotation: DefaultCompactNotation,
|
||||
portfolioCompactNotation: DefaultCompactNotation,
|
||||
},
|
||||
Views: &Views{
|
||||
Chart: NewChartView(),
|
||||
|
@ -48,6 +48,7 @@ type ConfigFileConfig struct {
|
||||
Colorscheme interface{} `toml:"colorscheme"`
|
||||
RefreshRate interface{} `toml:"refresh_rate"`
|
||||
CacheDir interface{} `toml:"cache_dir"`
|
||||
CompactNotation interface{} `toml:"compact_notation"`
|
||||
Table map[string]interface{} `toml:"table"`
|
||||
Chart map[string]interface{} `toml:"chart"`
|
||||
}
|
||||
@ -70,6 +71,7 @@ func (ct *Cointop) SetupConfig() error {
|
||||
ct.loadColorschemeFromConfig,
|
||||
ct.loadRefreshRateFromConfig,
|
||||
ct.loadCacheDirFromConfig,
|
||||
ct.loadCompactNotationFromConfig,
|
||||
ct.loadPriceAlertsFromConfig,
|
||||
ct.loadPortfolioFromConfig,
|
||||
}
|
||||
@ -215,10 +217,11 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
||||
var favoritesBySymbolIfc []interface{}
|
||||
favoritesMapIfc := map[string]interface{}{
|
||||
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.
|
||||
"symbols": favoritesBySymbolIfc,
|
||||
"names": favoritesIfc,
|
||||
"columns": ct.State.favoritesTableColumns,
|
||||
"character": ct.State.favoriteChar,
|
||||
"symbols": favoritesBySymbolIfc,
|
||||
"names": favoritesIfc,
|
||||
"columns": ct.State.favoritesTableColumns,
|
||||
"character": ct.State.favoriteChar,
|
||||
"compact_notation": ct.State.favoritesCompactNotation,
|
||||
}
|
||||
|
||||
var holdingsIfc [][]string
|
||||
@ -227,17 +230,18 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
||||
if !ok || entry.Coin == "" {
|
||||
continue
|
||||
}
|
||||
var amount string = strconv.FormatFloat(entry.Holdings, 'f', -1, 64)
|
||||
var coinName string = entry.Coin
|
||||
var tuple []string = []string{coinName, amount}
|
||||
amount := strconv.FormatFloat(entry.Holdings, 'f', -1, 64)
|
||||
coinName := entry.Coin
|
||||
tuple := []string{coinName, amount}
|
||||
holdingsIfc = append(holdingsIfc, tuple)
|
||||
}
|
||||
sort.Slice(holdingsIfc, func(i, j int) bool {
|
||||
return holdingsIfc[i][0] < holdingsIfc[j][0]
|
||||
})
|
||||
portfolioIfc := map[string]interface{}{
|
||||
"holdings": holdingsIfc,
|
||||
"columns": ct.State.portfolioTableColumns,
|
||||
"holdings": holdingsIfc,
|
||||
"columns": ct.State.portfolioTableColumns,
|
||||
"compact_notation": ct.State.portfolioCompactNotation,
|
||||
}
|
||||
|
||||
cmcIfc := map[string]interface{}{
|
||||
@ -264,6 +268,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
||||
tableMapIfc := map[string]interface{}{
|
||||
"columns": ct.State.coinsTableColumns,
|
||||
"keep_row_focus_on_sort": ct.State.keepRowFocusOnSort,
|
||||
"compact_notation": ct.State.tableCompactNotation,
|
||||
}
|
||||
|
||||
chartMapIfc := map[string]interface{}{
|
||||
@ -286,6 +291,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
||||
CacheDir: ct.State.cacheDir,
|
||||
Table: tableMapIfc,
|
||||
Chart: chartMapIfc,
|
||||
CompactNotation: ct.State.compactNotation,
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
@ -310,6 +316,11 @@ func (ct *Cointop) loadTableConfig() error {
|
||||
if ok {
|
||||
ct.State.keepRowFocusOnSort = keepRowFocusOnSortIfc.(bool)
|
||||
}
|
||||
|
||||
if compactNotation, ok := ct.config.Table["compact_notation"]; ok {
|
||||
ct.State.tableCompactNotation = compactNotation.(bool)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -458,6 +469,16 @@ func (ct *Cointop) loadCacheDirFromConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadCompactNotationFromConfig loads compact-notation setting from config file to struct
|
||||
func (ct *Cointop) loadCompactNotationFromConfig() error {
|
||||
log.Debug("loadCompactNotationFromConfig()")
|
||||
if compactNotation, ok := ct.config.CompactNotation.(bool); ok {
|
||||
ct.State.compactNotation = compactNotation
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAPIChoiceFromConfig loads API choices from config file to struct
|
||||
func (ct *Cointop) loadAPIChoiceFromConfig() error {
|
||||
log.Debug("loadAPIKeysFromConfig()")
|
||||
@ -480,6 +501,8 @@ func (ct *Cointop) loadFavoritesFromConfig() error {
|
||||
}
|
||||
ct.State.favoriteChar = favoriteChar
|
||||
}
|
||||
} else if k == "compact_notation" {
|
||||
ct.State.favoritesCompactNotation = valueIfc.(bool)
|
||||
}
|
||||
ifcs, ok := valueIfc.([]interface{})
|
||||
if !ok {
|
||||
@ -568,6 +591,8 @@ func (ct *Cointop) loadPortfolioFromConfig() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if key == "compact_notation" {
|
||||
ct.State.portfolioCompactNotation = valueIfc.(bool)
|
||||
} else {
|
||||
// Backward compatibility < v1.6.0
|
||||
holdings, err := ct.InterfaceToFloat64(valueIfc)
|
||||
|
@ -377,7 +377,7 @@ func (ct *Cointop) SetKeybindings() error {
|
||||
// TODO: use scrolling table
|
||||
keys := ct.SortedSupportedCurrencyConversions()
|
||||
for i, k := range keys {
|
||||
ct.SetKeybindingMod(rune(alphanumericcharacters[i]), gocui.ModNone, ct.Keyfn(ct.SetCurrencyConverstionFn(k)), ct.Views.Menu.Name())
|
||||
ct.SetKeybindingMod(alphanumericcharacters[i], gocui.ModNone, ct.Keyfn(ct.SetCurrencyConverstionFn(k)), ct.Views.Menu.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -19,8 +19,7 @@ type MarketbarView = ui.View
|
||||
|
||||
// NewMarketbarView returns a new marketbar view
|
||||
func NewMarketbarView() *MarketbarView {
|
||||
var view *MarketbarView = ui.NewView("marketbar")
|
||||
return view
|
||||
return ui.NewView("marketbar")
|
||||
}
|
||||
|
||||
// UpdateMarketbar updates the market bar view
|
||||
@ -41,6 +40,9 @@ func (ct *Cointop) UpdateMarketbar() error {
|
||||
total = math.Round(total*1e2) / 1e2
|
||||
totalstr = humanize.Monetaryf(total, 2)
|
||||
}
|
||||
if ct.State.compactNotation {
|
||||
totalstr = humanize.ScaleNumericf(total, 3)
|
||||
}
|
||||
|
||||
timeframe := ct.State.selectedChartRange
|
||||
chartname := ct.SelectedCoinName()
|
||||
@ -54,7 +56,7 @@ func (ct *Cointop) UpdateMarketbar() error {
|
||||
|
||||
var percentChange24H float64
|
||||
for _, p := range ct.GetPortfolioSlice() {
|
||||
n := ((p.Balance / total) * p.PercentChange24H)
|
||||
n := (p.Balance / total) * p.PercentChange24H
|
||||
if math.IsNaN(n) {
|
||||
continue
|
||||
}
|
||||
@ -154,12 +156,19 @@ func (ct *Cointop) UpdateMarketbar() error {
|
||||
separator2 = "\n" + offset
|
||||
}
|
||||
|
||||
marketCapStr := humanize.Monetaryf(market.TotalMarketCapUSD, 0)
|
||||
volumeStr := humanize.Monetaryf(market.Total24HVolumeUSD, 0)
|
||||
if ct.State.compactNotation {
|
||||
marketCapStr = humanize.ScaleNumericf(market.TotalMarketCapUSD, 3)
|
||||
volumeStr = humanize.ScaleNumericf(market.Total24HVolumeUSD, 3)
|
||||
}
|
||||
|
||||
content = fmt.Sprintf(
|
||||
"%sGlobal ▶ Market Cap: %s %s 24H Volume: %s %s BTC Dominance: %.2f%%",
|
||||
chartInfo,
|
||||
fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Monetaryf(market.TotalMarketCapUSD, 0)),
|
||||
fmt.Sprintf("%s%s", ct.CurrencySymbol(), marketCapStr),
|
||||
separator1,
|
||||
fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Monetaryf(market.Total24HVolumeUSD, 0)),
|
||||
fmt.Sprintf("%s%s", ct.CurrencySymbol(), volumeStr),
|
||||
separator2,
|
||||
market.BitcoinPercentageOfMarketCap,
|
||||
)
|
||||
|
@ -10,8 +10,7 @@ type MenuView = ui.View
|
||||
|
||||
// NewMenuView returns a new menu view
|
||||
func NewMenuView() *MenuView {
|
||||
var view *MenuView = ui.NewView("menu")
|
||||
return view
|
||||
return ui.NewView("menu")
|
||||
}
|
||||
|
||||
// HideMenu hides the menu view
|
||||
|
@ -413,7 +413,7 @@ func (ct *Cointop) GoToGlobalIndex(idx int) error {
|
||||
l := ct.TableRowsLen()
|
||||
atpage := idx / l
|
||||
ct.SetPage(atpage)
|
||||
rowIndex := (idx % l)
|
||||
rowIndex := idx % l
|
||||
ct.HighlightRow(rowIndex)
|
||||
ct.UpdateTable()
|
||||
return nil
|
||||
|
@ -983,7 +983,7 @@ func (ct *Cointop) PrintHoldings24HChange(options *TablePrintOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
n := ((entry.Balance / total) * entry.PercentChange24H)
|
||||
n := (entry.Balance / total) * entry.PercentChange24H
|
||||
if math.IsNaN(n) {
|
||||
continue
|
||||
}
|
||||
|
@ -14,8 +14,7 @@ type SearchFieldView = ui.View
|
||||
|
||||
// NewSearchFieldView returns a new search field view
|
||||
func NewSearchFieldView() *SearchFieldView {
|
||||
var view *SearchFieldView = ui.NewView("searchfield")
|
||||
return view
|
||||
return ui.NewView("searchfield")
|
||||
}
|
||||
|
||||
// InputView is structure for help view
|
||||
@ -23,8 +22,7 @@ type InputView = ui.View
|
||||
|
||||
// NewInputView returns a new help view
|
||||
func NewInputView() *InputView {
|
||||
var view *InputView = ui.NewView("input")
|
||||
return view
|
||||
return ui.NewView("input")
|
||||
}
|
||||
|
||||
// OpenSearch opens the search field
|
||||
|
@ -15,8 +15,7 @@ type StatusbarView = ui.View
|
||||
|
||||
// NewStatusbarView returns a new statusbar view
|
||||
func NewStatusbarView() *StatusbarView {
|
||||
var view *StatusbarView = ui.NewView("statusbar")
|
||||
return view
|
||||
return ui.NewView("statusbar")
|
||||
}
|
||||
|
||||
// UpdateStatusbar updates the statusbar view
|
||||
|
@ -14,8 +14,7 @@ type TableView = ui.View
|
||||
|
||||
// NewTableView returns a new table view
|
||||
func NewTableView() *TableView {
|
||||
var view *TableView = ui.NewView("table")
|
||||
return view
|
||||
return ui.NewView("table")
|
||||
}
|
||||
|
||||
const dots = "..."
|
||||
|
@ -21,6 +21,7 @@ var ArrowDown = "▼"
|
||||
type HeaderColumn struct {
|
||||
Slug string
|
||||
Label string
|
||||
ShortLabel string // only columns with a ShortLabel can be scaled?
|
||||
PlainLabel string
|
||||
}
|
||||
|
||||
@ -69,11 +70,13 @@ var HeaderColumns = map[string]*HeaderColumn{
|
||||
"market_cap": {
|
||||
Slug: "market_cap",
|
||||
Label: "[m]arket cap",
|
||||
ShortLabel: "[m]cap",
|
||||
PlainLabel: "market cap",
|
||||
},
|
||||
"24h_volume": {
|
||||
Slug: "24h_volume",
|
||||
Label: "24H [v]olume",
|
||||
ShortLabel: "24[v]",
|
||||
PlainLabel: "24H volume",
|
||||
},
|
||||
"1h_change": {
|
||||
@ -104,11 +107,13 @@ var HeaderColumns = map[string]*HeaderColumn{
|
||||
"total_supply": {
|
||||
Slug: "total_supply",
|
||||
Label: "[t]otal supply",
|
||||
ShortLabel: "[t]ot",
|
||||
PlainLabel: "total supply",
|
||||
},
|
||||
"available_supply": {
|
||||
Slug: "available_supply",
|
||||
Label: "[a]vailable supply",
|
||||
ShortLabel: "[a]vl",
|
||||
PlainLabel: "available supply",
|
||||
},
|
||||
"percent_holdings": {
|
||||
@ -123,13 +128,21 @@ var HeaderColumns = map[string]*HeaderColumn{
|
||||
},
|
||||
}
|
||||
|
||||
// GetLabel fetch the label to use for the heading (depends on configuration)
|
||||
func (ct *Cointop) GetLabel(h *HeaderColumn) string {
|
||||
// TODO: technically this should support nosort
|
||||
if ct.IsActiveTableCompactNotation() && h.ShortLabel != "" {
|
||||
return h.ShortLabel
|
||||
}
|
||||
return h.Label
|
||||
}
|
||||
|
||||
// TableHeaderView is structure for table header view
|
||||
type TableHeaderView = ui.View
|
||||
|
||||
// NewTableHeaderView returns a new table header view
|
||||
func NewTableHeaderView() *TableHeaderView {
|
||||
var view *TableHeaderView = ui.NewView("table_header")
|
||||
return view
|
||||
return ui.NewView("table_header")
|
||||
}
|
||||
|
||||
// GetActiveTableHeaders returns the list of active table headers
|
||||
@ -146,6 +159,22 @@ func (ct *Cointop) GetActiveTableHeaders() []string {
|
||||
return cols
|
||||
}
|
||||
|
||||
// GetActiveTableHeaders returns the list of active table headers
|
||||
func (ct *Cointop) IsActiveTableCompactNotation() bool {
|
||||
var compact bool
|
||||
switch ct.State.selectedView {
|
||||
case PortfolioView:
|
||||
compact = ct.State.portfolioCompactNotation
|
||||
case CoinsView:
|
||||
compact = ct.State.tableCompactNotation
|
||||
case FavoritesView:
|
||||
compact = ct.State.favoritesCompactNotation
|
||||
default:
|
||||
compact = ct.State.tableCompactNotation
|
||||
}
|
||||
return compact
|
||||
}
|
||||
|
||||
// UpdateTableHeader renders the table header
|
||||
func (ct *Cointop) UpdateTableHeader() error {
|
||||
log.Debug("UpdateTableHeader()")
|
||||
@ -176,7 +205,7 @@ func (ct *Cointop) UpdateTableHeader() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
label := hc.Label
|
||||
label := ct.GetLabel(hc)
|
||||
if noSort {
|
||||
label = hc.PlainLabel
|
||||
}
|
||||
@ -241,7 +270,7 @@ func (ct *Cointop) SetTableColumnWidth(header string, width int) {
|
||||
prev = prevIfc.(int)
|
||||
} else {
|
||||
hc := HeaderColumns[header]
|
||||
prev = utf8.RuneCountInString(hc.Label) + 1
|
||||
prev = utf8.RuneCountInString(ct.GetLabel(hc)) + 1
|
||||
switch header {
|
||||
case "price", "balance":
|
||||
prev++
|
||||
|
@ -357,6 +357,12 @@ draft: false
|
||||
|
||||
Supported columns relating to price change are `1h_change`, `24h_change`, `7d_change`, `30d_change`, `1y_change`
|
||||
|
||||
## How can I use K (thousand), M (million), B (billion), T (trillion) suffixes for shorter numbers?
|
||||
|
||||
There is a setting at the top-level of the configuration file called `compact_notation=true` which changes the marketbar values `market cap`, `volume` and `portfolio total value`.
|
||||
|
||||
The same setting can be applied at in the `[table]` section to impact the `24h_volume`, `market_cap`, `total_supply`, `available_supply` columns in the main coin view; and in the `[favorites]` section to change the same columns. The setting also changes the column names to be shorter.
|
||||
|
||||
## How can use a different config file other than the default?
|
||||
|
||||
Run cointop with the `--config` flag, eg `cointop --config="/path/to/config.toml"`, to use the specified file as the config.
|
||||
|
@ -7,7 +7,7 @@ draft: false
|
||||
|
||||
There are multiple ways you can install cointop depending on the platform you're on.
|
||||
|
||||
## From source (always latest and recommeded)
|
||||
## From source (always latest and recommended)
|
||||
|
||||
Make sure to have [go](https://golang.org/) (1.12+) installed, then do:
|
||||
|
||||
@ -69,7 +69,7 @@ Note: snaps don't work in Windows WSL. See this [issue thread](https://forum.sna
|
||||
|
||||
cointop is available as a [copr](https://copr.fedorainfracloud.org/coprs/miguelmota/cointop/) package.
|
||||
|
||||
First, enable the respository
|
||||
First, enable the repository
|
||||
|
||||
```bash
|
||||
sudo dnf copr enable miguelmota/cointop -y
|
||||
|
@ -37,10 +37,10 @@ type Service struct {
|
||||
|
||||
// NewCoinGecko new service
|
||||
func NewCoinGecko(config *Config) *Service {
|
||||
var maxResultsPerPage uint = 250 // absolute max
|
||||
var maxResults uint = 0
|
||||
var maxPages uint = 10
|
||||
var perPage uint = 100
|
||||
maxResultsPerPage := 250 // absolute max
|
||||
maxResults := uint(0)
|
||||
maxPages := uint(10)
|
||||
perPage := uint(100)
|
||||
if config.PerPage > 0 {
|
||||
perPage = config.PerPage
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -12,7 +13,7 @@ import (
|
||||
|
||||
// Numericf produces a string from of the given number with give fixed precision
|
||||
// in base 10 with thousands separators after every three orders of magnitude
|
||||
// using a thousands and decimal spearator according to LC_NUMERIC; defaulting "en".
|
||||
// using thousands and decimal separator according to LC_NUMERIC; defaulting "en".
|
||||
//
|
||||
// e.g. Numericf(834142.32, 2) -> "834,142.32"
|
||||
func Numericf(value float64, precision int) string {
|
||||
@ -21,16 +22,16 @@ func Numericf(value float64, precision int) string {
|
||||
|
||||
// Monetaryf produces a string from of the given number give minimum precision
|
||||
// in base 10 with thousands separators after every three orders of magnitude
|
||||
// using thousands and decimal spearator according to LC_MONETARY; defaulting "en".
|
||||
// using thousands and decimal separator according to LC_MONETARY; defaulting "en".
|
||||
//
|
||||
// e.g. Monetaryf(834142.3256, 2) -> "834,142.3256"
|
||||
func Monetaryf(value float64, precision int) string {
|
||||
return f(value, precision, "LC_MONETARY", false)
|
||||
}
|
||||
|
||||
// f formats given value v, with d decimal places using thousands and decimal
|
||||
// f formats given value, with precision decimal places using thousands and decimal
|
||||
// separator according to language found in given locale environment variable e.
|
||||
// If r is true the decimal places are fixed to the given d otherwise d is the
|
||||
// If fixed is true the decimal places are fixed to the given precision otherwise d is the
|
||||
// minimum of decimal places until the first 0.
|
||||
func f(value float64, precision int, envvar string, fixed bool) string {
|
||||
parts := strings.Split(strconv.FormatFloat(value, 'f', -1, 64), ".")
|
||||
@ -51,3 +52,47 @@ func f(value float64, precision int, envvar string, fixed bool) string {
|
||||
format := fmt.Sprintf("%%.%df", precision)
|
||||
return message.NewPrinter(lang).Sprintf(format, value)
|
||||
}
|
||||
|
||||
// Scale returns a scaled-down version of value and a suffix to add (M,B,etc.)
|
||||
func Scale(value float64) (float64, string) {
|
||||
type scalingUnit struct {
|
||||
value float64
|
||||
suffix string
|
||||
}
|
||||
|
||||
// quadrillion, quintrillion, sextillion, septillion, octillion, nonillion, and decillion
|
||||
var scales = [...]scalingUnit{
|
||||
{value: 1e12, suffix: "T"},
|
||||
{value: 1e9, suffix: "B"},
|
||||
{value: 1e6, suffix: "M"},
|
||||
{value: 1e3, suffix: "K"},
|
||||
}
|
||||
|
||||
for _, scale := range scales {
|
||||
if math.Abs(value) > scale.value {
|
||||
return value / scale.value, scale.suffix
|
||||
}
|
||||
}
|
||||
return value, ""
|
||||
}
|
||||
|
||||
// ScaleNumericf scales a large number down using a suffix, then formats it with the
|
||||
// prescribed number of significant digits.
|
||||
func ScaleNumericf(value float64, digits int) string {
|
||||
value, suffix := Scale(value)
|
||||
|
||||
// Round the scaled value to a certain number of significant figures
|
||||
var s string
|
||||
if math.Abs(value) < 1 {
|
||||
s = Numericf(value, digits)
|
||||
} else {
|
||||
numDigits := len(fmt.Sprintf("%.0f", math.Abs(value)))
|
||||
if numDigits >= digits {
|
||||
s = Numericf(value, 0)
|
||||
} else {
|
||||
s = Numericf(value, digits-numDigits)
|
||||
}
|
||||
}
|
||||
|
||||
return s + suffix
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -10,3 +11,43 @@ func TestMonetary(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestScale(t *testing.T) {
|
||||
scaleTests := map[float64]string{
|
||||
5.54 * 1e12: "5.5T",
|
||||
4.44 * 1e9: "4.4B",
|
||||
3.34 * 1e6: "3.3M",
|
||||
2.24 * 1e3: "2.2K",
|
||||
1.1: "1.1",
|
||||
0.06: "0.1",
|
||||
0.04: "0.0",
|
||||
-5.54 * 1e12: "-5.5T",
|
||||
}
|
||||
|
||||
for value, expected := range scaleTests {
|
||||
volScale, volSuffix := Scale(value)
|
||||
result := fmt.Sprintf("%.1f%s", volScale, volSuffix)
|
||||
if result != expected {
|
||||
t.Fatalf("Expected %f to scale to '%s' but got '%s'\n", value, expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScaleNumeric(t *testing.T) {
|
||||
scaleTests := map[float64]string{
|
||||
5.54 * 1e12: "5.5T",
|
||||
4.44 * 1e9: "4.4B",
|
||||
3.34 * 1e6: "3.3M",
|
||||
2.24 * 1e3: "2.2K",
|
||||
1.1: "1.1",
|
||||
0.0611: "0.06",
|
||||
-5.5432 * 1e12: "-5.5T",
|
||||
}
|
||||
|
||||
for value, expected := range scaleTests {
|
||||
result := ScaleNumericf(value, 2)
|
||||
if result != expected {
|
||||
t.Fatalf("Expected %f to scale to '%s' but got '%s'\n", value, expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func DamerauLevenshteinDistance(s1, s2 string) int {
|
||||
|
||||
// min returns the minimum number of passed int slices.
|
||||
func min(is ...int) int {
|
||||
min := int(math.MaxInt32)
|
||||
min := math.MaxInt32
|
||||
for _, v := range is {
|
||||
if min > v {
|
||||
min = v
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
"github.com/acarl005/stripansi"
|
||||
)
|
||||
|
||||
// AlignLeft align left
|
||||
func AlignLeft(t string, n int) string {
|
||||
// Left align left
|
||||
func Left(t string, n int) string {
|
||||
s := stripansi.Strip(t)
|
||||
slen := utf8.RuneCountInString(s)
|
||||
if slen > n {
|
||||
@ -19,8 +19,8 @@ func AlignLeft(t string, n int) string {
|
||||
return fmt.Sprintf("%s%s", t, strings.Repeat(" ", n-slen))
|
||||
}
|
||||
|
||||
// AlignRight align right
|
||||
func AlignRight(t string, n int) string {
|
||||
// Right align right
|
||||
func Right(t string, n int) string {
|
||||
s := stripansi.Strip(t)
|
||||
slen := utf8.RuneCountInString(s)
|
||||
if slen > n {
|
||||
@ -30,8 +30,8 @@ func AlignRight(t string, n int) string {
|
||||
return fmt.Sprintf("%s%s", strings.Repeat(" ", n-slen), t)
|
||||
}
|
||||
|
||||
// AlignCenter align center
|
||||
func AlignCenter(t string, n int) string {
|
||||
// Center align center
|
||||
func Center(t string, n int) string {
|
||||
s := stripansi.Strip(t)
|
||||
slen := utf8.RuneCountInString(s)
|
||||
if slen > n {
|
||||
|
@ -205,11 +205,11 @@ func (t *Table) Fprint(w io.Writer) {
|
||||
var s string
|
||||
switch c.align {
|
||||
case AlignLeft:
|
||||
s = align.AlignLeft(c.name+" ", c.width)
|
||||
s = align.Left(c.name+" ", c.width)
|
||||
case AlignRight:
|
||||
s = align.AlignRight(c.name+" ", c.width)
|
||||
s = align.Right(c.name+" ", c.width)
|
||||
case AlignCenter:
|
||||
s = align.AlignCenter(c.name+" ", c.width)
|
||||
s = align.Center(c.name+" ", c.width)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s", s)
|
||||
@ -237,11 +237,11 @@ func (t *Table) Fprint(w io.Writer) {
|
||||
var s string
|
||||
switch c.align {
|
||||
case AlignLeft:
|
||||
s = align.AlignLeft(v, c.width)
|
||||
s = align.Left(v, c.width)
|
||||
case AlignRight:
|
||||
s = align.AlignRight(v, c.width)
|
||||
s = align.Right(v, c.width)
|
||||
case AlignCenter:
|
||||
s = align.AlignCenter(v, c.width)
|
||||
s = align.Center(v, c.width)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s", s)
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
g.PercentColor = termui.ColorBlue
|
||||
*/
|
||||
|
||||
const ColorUndef Attribute = Attribute(^uint16(0))
|
||||
const ColorUndef = Attribute(^uint16(0))
|
||||
|
||||
type Gauge struct {
|
||||
Block
|
||||
|
Loading…
Reference in New Issue
Block a user