diff --git a/modules/exchangerates/settings.go b/modules/exchangerates/settings.go index 85ab17bc..3fa9bac1 100644 --- a/modules/exchangerates/settings.go +++ b/modules/exchangerates/settings.go @@ -15,6 +15,8 @@ const ( type Settings struct { common *cfg.Common + precision int `help:"How many decimal places to display." optional:"true"` + rates map[string][]string `help:"Defines what currency rates we want to know about"` order []string } @@ -24,6 +26,8 @@ func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *co settings := Settings{ common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), + precision: ymlConfig.UInt("precision", 7), + rates: map[string][]string{}, order: []string{}, } diff --git a/modules/exchangerates/widget.go b/modules/exchangerates/widget.go index 809b00ef..1aaaba91 100644 --- a/modules/exchangerates/widget.go +++ b/modules/exchangerates/widget.go @@ -1,11 +1,12 @@ -// Package exchangerates package exchangerates import ( "fmt" + "regexp" "github.com/rivo/tview" "github.com/wtfutil/wtf/view" + "github.com/wtfutil/wtf/wtf" ) type Widget struct { @@ -50,22 +51,36 @@ func (widget *Widget) Render() { /* -------------------- Unexported Functions -------------------- */ func (widget *Widget) content() (string, string, bool) { - out := "" - if widget.err != nil { - out = widget.err.Error() - } else { - for base, rates := range widget.settings.rates { - prefix := fmt.Sprintf("[%s]1 %s[white] = ", widget.settings.common.Colors.Subheading, base) + return widget.CommonSettings().Title, widget.err.Error(), false + } - for idx, cur := range rates { - rate := widget.rates[base][cur] + out := "" + idx := 0 + for base, rates := range widget.settings.rates { + for _, cur := range rates { + rate := widget.rates[base][cur] - out += prefix - out += fmt.Sprintf("[%s]%f %s[white]\n", widget.CommonSettings().RowColor(idx), rate, cur) - } + out += fmt.Sprintf( + "[%s]1 %s = %s %s[white]\n", + widget.CommonSettings().RowColor(idx), + base, + widget.formatConversionRate(rate), + cur, + ) + + idx++ } } return widget.CommonSettings().Title, out, false } + +// formatConversionRate takes the raw conversion float and formats it to the precision the +// user specifies in their config (or to the default value) +func (widget *Widget) formatConversionRate(rate float64) string { + rate = wtf.TruncateFloat64(rate, widget.settings.precision) + + r, _ := regexp.Compile(`\.?0*$`) + return r.ReplaceAllString(fmt.Sprintf("%10.7f", rate), "") +} diff --git a/wtf/numbers.go b/wtf/numbers.go new file mode 100644 index 00000000..2142ce79 --- /dev/null +++ b/wtf/numbers.go @@ -0,0 +1,14 @@ +package wtf + +import "math" + +// Round rounds a float to an integer +func Round(num float64) int { + return int(num + math.Copysign(0.5, num)) +} + +// TruncateFloat64 truncates the decimal places of a float64 to the specified precision +func TruncateFloat64(num float64, precision int) float64 { + output := math.Pow(10, float64(precision)) + return float64(Round(num*output)) / output +} diff --git a/wtf/numbers_test.go b/wtf/numbers_test.go new file mode 100644 index 00000000..04764d27 --- /dev/null +++ b/wtf/numbers_test.go @@ -0,0 +1,78 @@ +package wtf + +import ( + "testing" + + "gotest.tools/assert" +) + +func Test_Round(t *testing.T) { + tests := []struct { + name string + input float64 + expected int + }{ + { + name: "negative", + input: -3, + expected: -3, + }, + { + name: "integer", + input: 3, + expected: 3, + }, + { + name: "float down", + input: 3.123456, + expected: 3, + }, + { + name: "float up", + input: 3.998786, + expected: 4, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := Round(tt.input) + assert.Equal(t, tt.expected, actual) + }) + } +} + +func Test_TruncateFloat(t *testing.T) { + tests := []struct { + name string + input float64 + precision int + expected float64 + }{ + { + name: "negative precision", + input: 23.234567, + precision: -2, + expected: 0, + }, + { + name: "zero precision", + input: 23.234567, + precision: 0, + expected: 23, + }, + { + name: "positive precision", + input: 23.234567, + precision: 2, + expected: 23.23, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := TruncateFloat64(tt.input, tt.precision) + assert.Equal(t, tt.expected, actual) + }) + } +}