From 7d06a536eb045c278947c240012ce245687625a5 Mon Sep 17 00:00:00 2001 From: afh Date: Sun, 18 Apr 2021 17:32:44 +0200 Subject: [PATCH] pkg/humanize: Replace Commaf with Numericf and Monetaryf to allow for locale aware formatting of numbers. --- cointop/coins_table.go | 18 +++++----- cointop/marketbar.go | 8 ++--- cointop/portfolio.go | 16 ++++----- cointop/price.go | 2 +- cointop/price_alerts.go | 6 ++-- go.mod | 2 ++ pkg/humanize/humanize.go | 72 +++++++++++++++++++--------------------- 7 files changed, 62 insertions(+), 62 deletions(-) diff --git a/cointop/coins_table.go b/cointop/coins_table.go index cd663e7..5abda59 100644 --- a/cointop/coins_table.go +++ b/cointop/coins_table.go @@ -120,7 +120,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { Text: symbol, }) case "price": - text := humanize.Commaf(coin.Price) + text := humanize.Monetaryf(coin.Price, 2) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -132,7 +132,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { Text: text, }) case "24h_volume": - text := humanize.Commaf(coin.Volume24H) + text := humanize.Monetaryf(coin.Volume24H, 0) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -151,7 +151,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { if coin.PercentChange1H < 0 { color1h = ct.colorscheme.TableColumnChangeDown } - text := fmt.Sprintf("%.2f%%", coin.PercentChange1H) + text := fmt.Sprintf("%v%%", humanize.Numericf(coin.PercentChange1H, 2)) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -170,7 +170,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { if coin.PercentChange24H < 0 { color24h = ct.colorscheme.TableColumnChangeDown } - text := fmt.Sprintf("%.2f%%", coin.PercentChange24H) + text := fmt.Sprintf("%v%%", humanize.Numericf(coin.PercentChange24H, 2)) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -189,7 +189,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { if coin.PercentChange7D < 0 { color7d = ct.colorscheme.TableColumnChangeDown } - text := fmt.Sprintf("%.2f%%", coin.PercentChange7D) + text := fmt.Sprintf("%v%%", humanize.Numericf(coin.PercentChange7D, 2)) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -208,7 +208,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { if coin.PercentChange30D < 0 { color30d = ct.colorscheme.TableColumnChangeDown } - text := fmt.Sprintf("%.2f%%", coin.PercentChange30D) + text := fmt.Sprintf("%v%%", humanize.Numericf(coin.PercentChange30D, 2)) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -220,7 +220,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { Text: text, }) case "market_cap": - text := humanize.Commaf(coin.MarketCap) + text := humanize.Monetaryf(coin.MarketCap, 0) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -232,7 +232,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { Text: text, }) case "total_supply": - text := humanize.Commaf(coin.TotalSupply) + text := humanize.Numericf(coin.TotalSupply, 0) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, @@ -244,7 +244,7 @@ func (ct *Cointop) GetCoinsTable() *table.Table { Text: text, }) case "available_supply": - text := humanize.Commaf(coin.AvailableSupply) + text := humanize.Numericf(coin.AvailableSupply, 0) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, diff --git a/cointop/marketbar.go b/cointop/marketbar.go index 67917be..002b343 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -35,10 +35,10 @@ func (ct *Cointop) UpdateMarketbar() error { if ct.IsPortfolioVisible() { ct.State.marketBarHeight = 1 total := ct.GetPortfolioTotal() - totalstr := humanize.Commaf(total) + totalstr := humanize.Monetaryf(total, 2) if !(ct.State.currencyConversion == "BTC" || ct.State.currencyConversion == "ETH" || total < 1) { total = math.Round(total*1e2) / 1e2 - totalstr = humanize.Commaf2(total) + totalstr = humanize.Monetaryf(total, 2) } timeframe := ct.State.selectedChartRange @@ -149,9 +149,9 @@ func (ct *Cointop) UpdateMarketbar() error { content = fmt.Sprintf( "%sGlobal ▶ Market Cap: %s %s 24H Volume: %s %s BTC Dominance: %.2f%%", chartInfo, - fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Commaf0(market.TotalMarketCapUSD)), + fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Monetaryf(market.TotalMarketCapUSD, 0)), separator1, - fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Commaf0(market.Total24HVolumeUSD)), + fmt.Sprintf("%s%s", ct.CurrencySymbol(), humanize.Monetaryf(market.Total24HVolumeUSD, 0)), separator2, market.BitcoinPercentageOfMarketCap, ) diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 7a3f0a6..ee764a8 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -125,7 +125,7 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { Text: symbol, }) case "price": - text := humanize.Commaf(coin.Price) + text := humanize.Monetaryf(coin.Price, 2) symbolPadding := 1 ct.SetTableColumnWidth(header, utf8.RuneCountInString(text)+symbolPadding) ct.SetTableColumnAlignLeft(header, false) @@ -150,7 +150,7 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { Text: text, }) case "balance": - text := humanize.Commaf(coin.Balance) + text := humanize.Monetaryf(coin.Balance, 2) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) colorBalance := ct.colorscheme.TableColumnPrice @@ -675,11 +675,11 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { records[i] = []string{ entry.Name, entry.Symbol, - fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Price)), - humanize.Commaf(entry.Holdings), - fmt.Sprintf("%s%s", symbol, humanize.Commaf(entry.Balance)), - fmt.Sprintf("%.2f%%", entry.PercentChange24H), - fmt.Sprintf("%.2f%%", percentHoldings), + fmt.Sprintf("%s%s", symbol, humanize.Monetaryf(entry.Price, 2)), + humanize.Numericf(entry.Holdings, 2), + fmt.Sprintf("%s%s", symbol, humanize.Monetaryf(entry.Balance, 2)), + humanize.Numericf(entry.PercentChange24H, 2), + humanize.Numericf(percentHoldings, 2), } } else { records[i] = []string{ @@ -785,7 +785,7 @@ func (ct *Cointop) PrintTotalHoldings(options *TablePrintOptions) error { value := strconv.FormatFloat(total, 'f', -1, 64) if humanReadable { - value = fmt.Sprintf("%s%s", symbol, humanize.Commaf(total)) + value = fmt.Sprintf("%s%s", symbol, humanize.Monetaryf(total, 2)) } if format == "csv" { diff --git a/cointop/price.go b/cointop/price.go index 2d43304..c75e2dc 100644 --- a/cointop/price.go +++ b/cointop/price.go @@ -69,7 +69,7 @@ func GetCoinPrices(config *PricesConfig) ([]string, error) { } symbol := CurrencySymbol(config.Currency) - value := fmt.Sprintf("%s%s", symbol, humanize.Commaf(price)) + value := fmt.Sprintf("%s%s", symbol, humanize.Monetaryf(price, 2)) prices = append(prices, value) } diff --git a/cointop/price_alerts.go b/cointop/price_alerts.go index 17b7b5c..1ef02f5 100644 --- a/cointop/price_alerts.go +++ b/cointop/price_alerts.go @@ -96,7 +96,7 @@ func (ct *Cointop) GetPriceAlertsTable() *table.Table { }) case "target_price": - targetPrice := fmt.Sprintf("%s %s", entry.Operator, humanize.Commaf(entry.TargetPrice)) + targetPrice := fmt.Sprintf("%s %s", entry.Operator, humanize.Monetaryf(entry.TargetPrice, 2)) ct.SetTableColumnWidthFromString(header, targetPrice) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, &table.RowCell{ @@ -107,7 +107,7 @@ func (ct *Cointop) GetPriceAlertsTable() *table.Table { Text: targetPrice, }) case "price": - text := humanize.Commaf(coin.Price) + text := humanize.Monetaryf(coin.Price, 2) ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) rowCells = append(rowCells, &table.RowCell{ @@ -187,7 +187,7 @@ func (ct *Cointop) CheckPriceAlert(alert *PriceAlert) error { } var msg string title := "Cointop Alert" - priceStr := fmt.Sprintf("%s%s (%s%s)", ct.CurrencySymbol(), humanize.Commaf(alert.TargetPrice), ct.CurrencySymbol(), humanize.Commaf(coin.Price)) + priceStr := fmt.Sprintf("%s%s (%s%s)", ct.CurrencySymbol(), humanize.Numericf(alert.TargetPrice, 2), ct.CurrencySymbol(), humanize.Monetaryf(coin.Price, 2)) if alert.Operator == ">" { if coin.Price > alert.TargetPrice { msg = fmt.Sprintf("%s price is greater than %v", alert.CoinName, priceStr) diff --git a/go.mod b/go.mod index 379455a..099dcc9 100644 --- a/go.mod +++ b/go.mod @@ -27,3 +27,5 @@ require ( ) go 1.13 + +replace github.com/miguelmota/cointop/pkg/humanize => ./pkg/humanize diff --git a/pkg/humanize/humanize.go b/pkg/humanize/humanize.go index 0eb8ee0..61a0660 100644 --- a/pkg/humanize/humanize.go +++ b/pkg/humanize/humanize.go @@ -1,7 +1,8 @@ package humanize import ( - "bytes" + "fmt" + "os" "strconv" "strings" @@ -9,47 +10,44 @@ import ( "golang.org/x/text/message" ) -// Commaf produces a string form of the given number in base 10 with -// commas after every three orders of magnitude. +// 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". // -// e.g. Commaf(834142.32) -> 834,142.32 -func Commaf(v float64) string { - buf := &bytes.Buffer{} - if v < 0 { - buf.Write([]byte{'-'}) - v = 0 - v - } +// e.g. Numericf(834142.32, 2) -> "834,142.32" +func Numericf(value float64, precision int) string { + return f(value, precision, "LC_NUMERIC", true) +} - comma := []byte{','} +// 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". +// +// e.g. Monetaryf(834142.3256, 2) -> "834,142.3256" +func Monetaryf(value float64, precision int) string { + return f(value, precision, "LC_MONETARY", false) +} - parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") - pos := 0 - if len(parts[0])%3 != 0 { - pos += len(parts[0]) % 3 - buf.WriteString(parts[0][:pos]) - buf.Write(comma) +// f formats given value v, with d 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 +// 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), ".") + if !fixed && len(parts) > 1 { + for ; precision < len(parts[1]); precision += 1 { + if parts[1][precision] == '0' { + break + } + } } - for ; pos < len(parts[0]); pos += 3 { - buf.WriteString(parts[0][pos : pos+3]) - buf.Write(comma) - } - buf.Truncate(buf.Len() - 1) - if len(parts) > 1 { - buf.Write([]byte{'.'}) - buf.WriteString(parts[1]) + envlang, ok := os.LookupEnv(envvar) + if !ok { + envlang = "en" } - return buf.String() -} - -// Commaf2 ... -func Commaf2(v float64) string { - p := message.NewPrinter(language.English) - return p.Sprintf("%.2f", v) -} + lang := language.Make(envlang) -// Commaf0 ... -func Commaf0(v float64) string { - p := message.NewPrinter(language.English) - return p.Sprintf("%.0f", v) + format := fmt.Sprintf("%%.%df", precision) + return message.NewPrinter(lang).Sprintf(format, value) }