Use variadic args for Value factory options

Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
This commit is contained in:
Xabier Larrakoetxea 2019-05-03 07:14:01 +02:00
parent a8931e2820
commit c1bf776dba
No known key found for this signature in database
GPG Key ID: FDAD7FD8275E1B32
6 changed files with 66 additions and 40 deletions

View File

@ -73,7 +73,7 @@ type YProperties struct {
// ScaleMode determines how the Y axis scales. // ScaleMode determines how the Y axis scales.
ScaleMode YScaleMode ScaleMode YScaleMode
// ValueFormatter is the formatter used to format numeric values to string representation. // ValueFormatter is the formatter used to format numeric values to string representation.
ValueFormatter valueFormatter ValueFormatter func(float64) string
} }
// NewYDetails retrieves details about the Y axis required to draw it on a // NewYDetails retrieves details about the Y axis required to draw it on a

View File

@ -213,8 +213,8 @@ func TestY(t *testing.T) {
End: image.Point{1, 2}, End: image.Point{1, 2},
Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, testValueFormatter), Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, testValueFormatter),
Labels: []*Label{ Labels: []*Label{
{NewFormattedValue(0, nonZeroDecimals, testValueFormatter), image.Point{0, 1}}, {NewValue(0, nonZeroDecimals, ValueFormatter(testValueFormatter)), image.Point{0, 1}},
{NewFormattedValue(1.72, nonZeroDecimals, testValueFormatter), image.Point{0, 0}}, {NewValue(1.72, nonZeroDecimals, ValueFormatter(testValueFormatter)), image.Point{0, 0}},
}, },
}, },
}, },

View File

@ -69,7 +69,7 @@ type YScale struct {
// valueFormatter is the value formatter used for the labels // valueFormatter is the value formatter used for the labels
// represented by the values on the scale. // represented by the values on the scale.
valueFormatter valueFormatter valueFormatter func(float64) string
} }
// String implements fmt.Stringer. // String implements fmt.Stringer.
@ -82,7 +82,7 @@ func (ys *YScale) String() string {
// calculated scale, see NewValue for details. // calculated scale, see NewValue for details.
// Max must be greater or equal to min. The graphHeight must be a positive // Max must be greater or equal to min. The graphHeight must be a positive
// number. // number.
func NewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode, valueFormatter valueFormatter) (*YScale, error) { func NewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode, valueFormatter func(float64) string) (*YScale, error) {
if max < min { if max < min {
return nil, fmt.Errorf("max(%v) cannot be less than min(%v)", max, min) return nil, fmt.Errorf("max(%v) cannot be less than min(%v)", max, min)
} }
@ -199,12 +199,13 @@ func (ys *YScale) CellLabel(y int) (*Value, error) {
// yScaleNewValue is a helper method to get new values for the y scale // yScaleNewValue is a helper method to get new values for the y scale
// that selects the correct value factory method depending on the passed // that selects the correct value factory method depending on the passed
// arguments. // arguments.
func yScaleNewValue(value float64, nonZeroDecimals int, valueFormatter valueFormatter) *Value { func yScaleNewValue(value float64, nonZeroDecimals int, valueFormatter func(float64) string) *Value {
opts := []ValueOption{}
if valueFormatter != nil { if valueFormatter != nil {
return NewFormattedValue(value, nonZeroDecimals, valueFormatter) opts = append(opts, ValueFormatter(valueFormatter))
} }
return NewValue(value, nonZeroDecimals) return NewValue(value, nonZeroDecimals, opts...)
} }
// XScale is the scale of the X axis. // XScale is the scale of the X axis.

View File

@ -22,8 +22,8 @@ import (
) )
// mustNewYScale returns a new YScale or panics. // mustNewYScale returns a new YScale or panics.
func mustNewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode, formatter valueFormatter) *YScale { func mustNewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode, valueFormatter func(float64) string) *YScale {
s, err := NewYScale(min, max, graphHeight, nonZeroDecimals, mode, formatter) s, err := NewYScale(min, max, graphHeight, nonZeroDecimals, mode, valueFormatter)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -23,7 +23,30 @@ import (
"github.com/mum4k/termdash/internal/numbers" "github.com/mum4k/termdash/internal/numbers"
) )
type valueFormatter = func(float64) string // ValueOption is used to provide options to the NewValue function.
type ValueOption interface {
// set sets the provided option.
set(*valueOptions)
}
type valueOptions struct {
formatter func(v float64) string
}
// valueOption implements ValueOption.
type valueOption func(opts *valueOptions)
// set implements ValueOption.set.
func (vo valueOption) set(opts *valueOptions) {
vo(opts)
}
// ValueFormatter sets a custom formatter for the value.
func ValueFormatter(formatter func(float64) string) ValueOption {
return valueOption(func(opts *valueOptions) {
opts.formatter = formatter
})
}
// Value represents one value. // Value represents one value.
type Value struct { type Value struct {
@ -38,10 +61,10 @@ type Value struct {
// NonZeroDecimals indicates the rounding precision used, it is provided on // NonZeroDecimals indicates the rounding precision used, it is provided on
// a call to newValue. // a call to newValue.
NonZeroDecimals int NonZeroDecimals int
// Formatter will format value to a string representation of the value,
// if Formatter is not present it will fallback to default format.
Formatter valueFormatter
// formatter will format value to a string representation of the value,
// if Formatter is not present it will fallback to default format.
formatter func(float64) string
// text value if this value was constructed using NewTextValue. // text value if this value was constructed using NewTextValue.
text string text string
} }
@ -53,20 +76,19 @@ func (v *Value) String() string {
// NewValue returns a new instance representing the provided value, rounding // NewValue returns a new instance representing the provided value, rounding
// the value up to the specified number of non-zero decimal places. // the value up to the specified number of non-zero decimal places.
func NewValue(v float64, nonZeroDecimals int) *Value { func NewValue(v float64, nonZeroDecimals int, opts ...ValueOption) *Value {
return NewFormattedValue(v, nonZeroDecimals, nil) opt := &valueOptions{}
} for _, o := range opts {
o.set(opt)
}
// NewFormattedValue returns a new instance representing the provided value,
// using a value formatter.
func NewFormattedValue(v float64, nonZeroDecimals int, formatter valueFormatter) *Value {
r, zd := numbers.RoundToNonZeroPlaces(v, nonZeroDecimals) r, zd := numbers.RoundToNonZeroPlaces(v, nonZeroDecimals)
return &Value{ return &Value{
Value: v, Value: v,
Rounded: r, Rounded: r,
ZeroDecimals: zd, ZeroDecimals: zd,
NonZeroDecimals: nonZeroDecimals, NonZeroDecimals: nonZeroDecimals,
Formatter: formatter, formatter: opt.formatter,
} }
} }
@ -85,19 +107,19 @@ func (v *Value) Text() string {
return v.text return v.text
} }
if v.Formatter != nil { if v.formatter != nil {
return v.Formatter(v.Value) return v.formatter(v.Value)
} }
return v.defaultFormatter(v.Rounded) return defaultFormatter(v.Rounded, v.NonZeroDecimals, v.ZeroDecimals)
} }
func (v *Value) defaultFormatter(value float64) string { func defaultFormatter(value float64, nonZeroDecimals, zeroDecimals int) string {
if math.Ceil(value) == value { if math.Ceil(value) == value {
return fmt.Sprintf("%.0f", value) return fmt.Sprintf("%.0f", value)
} }
format := fmt.Sprintf("%%.%df", v.NonZeroDecimals+v.ZeroDecimals) format := fmt.Sprintf("%%.%df", nonZeroDecimals+zeroDecimals)
t := fmt.Sprintf(format, value) t := fmt.Sprintf(format, value)
if len(t) > 10 { if len(t) > 10 {
t = fmt.Sprintf("%.2e", value) t = fmt.Sprintf("%.2e", value)

View File

@ -22,10 +22,13 @@ import (
) )
func TestValue(t *testing.T) { func TestValue(t *testing.T) {
formatter := func(float64) string { return "test" }
tests := []struct { tests := []struct {
desc string desc string
float float64 float float64
nonZeroDecimals int nonZeroDecimals int
formatter func(float64) string
want *Value want *Value
}{ }{
{ {
@ -61,11 +64,24 @@ func TestValue(t *testing.T) {
NonZeroDecimals: 0, NonZeroDecimals: 0,
}, },
}, },
{
desc: "formatter value when value formatter as option",
float: 1.01234,
nonZeroDecimals: 0,
formatter: formatter,
want: &Value{
Value: 1.01234,
Rounded: 1.01234,
ZeroDecimals: 1,
NonZeroDecimals: 0,
formatter: formatter,
},
},
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
got := NewValue(tc.float, tc.nonZeroDecimals) got := NewValue(tc.float, tc.nonZeroDecimals, ValueFormatter(tc.formatter))
if diff := pretty.Compare(tc.want, got); diff != "" { if diff := pretty.Compare(tc.want, got); diff != "" {
t.Errorf("NewValue => unexpected diff (-want, +got):\n%s", diff) t.Errorf("NewValue => unexpected diff (-want, +got):\n%s", diff)
} }
@ -124,16 +140,3 @@ func TestNewTextValue(t *testing.T) {
t.Errorf("v.Text => got %q, want %q", got, want) t.Errorf("v.Text => got %q, want %q", got, want)
} }
} }
func TestFormattedValue(t *testing.T) {
const (
value = 42
want = "test"
)
v := NewFormattedValue(value, 2, func(float64) string { return "test" })
got := v.Text()
if got != want {
t.Errorf("v.Text => got %q, want %q", got, want)
}
}