From 371156a9db081d7c95b3e23a93c36fefafaddee7 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 27 Apr 2025 01:41:08 +0200 Subject: [PATCH 1/7] feat: float32, float64 --- args.go | 24 +++++- flag_float.go | 55 +++++++++---- flag_float_slice.go | 33 +++++++- flag_float_slice_test.go | 156 ++++++++++++++++++++++++++++++++++++ flag_float_test.go | 169 +++++++++++++++++++++++++++++++++++++++ flag_test.go | 13 +-- 6 files changed, 424 insertions(+), 26 deletions(-) create mode 100644 flag_float_slice_test.go create mode 100644 flag_float_test.go diff --git a/args.go b/args.go index 3a24206348..918afb2ed0 100644 --- a/args.go +++ b/args.go @@ -226,7 +226,9 @@ func (a *ArgumentsBase[T, C, VC]) Get() any { } type ( - FloatArg = ArgumentBase[float64, NoConfig, floatValue] + FloatArg = ArgumentBase[float64, NoConfig, floatValue[float64]] + Float32Arg = ArgumentBase[float32, NoConfig, floatValue[float32]] + Float64Arg = ArgumentBase[float64, NoConfig, floatValue[float64]] IntArg = ArgumentBase[int, IntegerConfig, intValue[int]] Int8Arg = ArgumentBase[int8, IntegerConfig, intValue[int8]] Int16Arg = ArgumentBase[int16, IntegerConfig, intValue[int16]] @@ -241,7 +243,9 @@ type ( Uint32Arg = ArgumentBase[uint32, IntegerConfig, uintValue[uint32]] Uint64Arg = ArgumentBase[uint64, IntegerConfig, uintValue[uint64]] - FloatArgs = ArgumentsBase[float64, NoConfig, floatValue] + FloatArgs = ArgumentsBase[float64, NoConfig, floatValue[float64]] + Float32Args = ArgumentsBase[float32, NoConfig, floatValue[float32]] + Float64Args = ArgumentsBase[float64, NoConfig, floatValue[float64]] IntArgs = ArgumentsBase[int, IntegerConfig, intValue[int]] Int8Args = ArgumentsBase[int8, IntegerConfig, intValue[int8]] Int16Args = ArgumentsBase[int16, IntegerConfig, intValue[int16]] @@ -293,6 +297,22 @@ func (c *Command) FloatArgs(name string) []float64 { return arg[[]float64](name, c) } +func (c *Command) Float32Arg(name string) float32 { + return arg[float32](name, c) +} + +func (c *Command) Float32Args(name string) []float32 { + return arg[[]float32](name, c) +} + +func (c *Command) Float64Arg(name string) float64 { + return arg[float64](name, c) +} + +func (c *Command) Float64Args(name string) []float64 { + return arg[[]float64](name, c) +} + func (c *Command) IntArg(name string) int { return arg[int](name, c) } diff --git a/flag_float.go b/flag_float.go index 406b55b64d..da4555e895 100644 --- a/flag_float.go +++ b/flag_float.go @@ -2,44 +2,71 @@ package cli import ( "strconv" + "unsafe" ) -type FloatFlag = FlagBase[float64, NoConfig, floatValue] +type ( + FloatFlag = FlagBase[float64, NoConfig, floatValue[float64]] + Float32Flag = FlagBase[float32, NoConfig, floatValue[float32]] + Float64Flag = FlagBase[float64, NoConfig, floatValue[float64]] +) -// -- float64 Value -type floatValue float64 +// -- float Value +type floatValue[T float32 | float64] struct { + val *T +} // Below functions are to satisfy the ValueCreator interface -func (f floatValue) Create(val float64, p *float64, c NoConfig) Value { +func (f floatValue[T]) Create(val T, p *T, c NoConfig) Value { *p = val - return (*floatValue)(p) + + return &floatValue[T]{val: p} } -func (f floatValue) ToString(b float64) string { - return strconv.FormatFloat(b, 'g', -1, 64) +func (f floatValue[T]) ToString(b T) string { + return strconv.FormatFloat(float64(b), 'g', -1, int(unsafe.Sizeof(T(0))*8)) } // Below functions are to satisfy the flag.Value interface -func (f *floatValue) Set(s string) error { - v, err := strconv.ParseFloat(s, 64) +func (f *floatValue[T]) Set(s string) error { + v, err := strconv.ParseFloat(s, int(unsafe.Sizeof(T(0))*8)) if err != nil { return err } - *f = floatValue(v) - return err + *f.val = T(v) + return nil } -func (f *floatValue) Get() any { return float64(*f) } +func (f *floatValue[T]) Get() any { return *f.val } -func (f *floatValue) String() string { return strconv.FormatFloat(float64(*f), 'g', -1, 64) } +func (f *floatValue[T]) String() string { + return strconv.FormatFloat(float64(*f.val), 'g', -1, int(unsafe.Sizeof(T(0))*8)) +} // Float looks up the value of a local FloatFlag, returns // 0 if not found func (cmd *Command) Float(name string) float64 { - if v, ok := cmd.Value(name).(float64); ok { + return getFloat[float64](cmd, name) +} + +// Float32 looks up the value of a local Float32Flag, returns +// 0 if not found +func (cmd *Command) Float32(name string) float32 { + return getFloat[float32](cmd, name) +} + +// Float64 looks up the value of a local Float32Flag, returns +// 0 if not found +func (cmd *Command) Float64(name string) float64 { + return getFloat[float64](cmd, name) +} + +func getFloat[T float32 | float64](cmd *Command, name string) T { + if v, ok := cmd.Value(name).(T); ok { tracef("float available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name) + return v } diff --git a/flag_float_slice.go b/flag_float_slice.go index 1ba65306ff..76ea149140 100644 --- a/flag_float_slice.go +++ b/flag_float_slice.go @@ -1,17 +1,42 @@ package cli type ( - FloatSlice = SliceBase[float64, NoConfig, floatValue] - FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice] + FloatSlice = SliceBase[float64, NoConfig, floatValue[float64]] + Float32Slice = SliceBase[float32, NoConfig, floatValue[float32]] + Float64Slice = SliceBase[float64, NoConfig, floatValue[float64]] + FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice] + Float32SliceFlag = FlagBase[[]float32, NoConfig, Float32Slice] + Float64SliceFlag = FlagBase[[]float64, NoConfig, Float64Slice] ) -var NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue] +var ( + NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue[float64]] + NewFloat32Slice = NewSliceBase[float32, NoConfig, floatValue[float32]] + NewFloat64Slice = NewSliceBase[float64, NoConfig, floatValue[float64]] +) // FloatSlice looks up the value of a local FloatSliceFlag, returns // nil if not found func (cmd *Command) FloatSlice(name string) []float64 { - if v, ok := cmd.Value(name).([]float64); ok { + return getFloatSlice[float64](cmd, name) +} + +// Float32Slice looks up the value of a local Float32Slice, returns +// nil if not found +func (cmd *Command) Float32Slice(name string) []float32 { + return getFloatSlice[float32](cmd, name) +} + +// Float64Slice looks up the value of a local Float64SliceFlag, returns +// nil if not found +func (cmd *Command) Float64Slice(name string) []float64 { + return getFloatSlice[float64](cmd, name) +} + +func getFloatSlice[T float32 | float64](cmd *Command, name string) []T { + if v, ok := cmd.Value(name).([]T); ok { tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name) + return v } diff --git a/flag_float_slice_test.go b/flag_float_slice_test.go new file mode 100644 index 0000000000..299d8e9270 --- /dev/null +++ b/flag_float_slice_test.go @@ -0,0 +1,156 @@ +package cli + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCommand_FloatSlice(t *testing.T) { + tests := []struct { + name string + flag Flag + arguments []string + expect []float64 + expectErr bool + }{ + { + flag: &FloatSliceFlag{ + Name: "numbers", + }, + arguments: []string{"--numbers", "1,2,3,4"}, + expect: []float64{1, 2, 3, 4}, + }, + { + flag: &FloatSliceFlag{ + Name: "numbers", + }, + arguments: []string{"--numbers", "1,2", "--numbers", "3,4"}, + expect: []float64{1, 2, 3, 4}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &Command{ + Name: "mock", + Flags: []Flag{tt.flag}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := cmd.Run(buildTestContext(t), append([]string{"mock"}, tt.arguments...)) + + if tt.expectErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + + for _, name := range tt.flag.Names() { + assert.Equalf(t, tt.expect, cmd.FloatSlice(name), "FloatSlice(%v)", name) + } + }) + } +} + +func TestCommand_Float32Slice(t *testing.T) { + tests := []struct { + name string + flag Flag + arguments []string + expect []float32 + expectErr bool + }{ + { + flag: &Float32SliceFlag{ + Name: "numbers", + }, + arguments: []string{"--numbers", "1,2,3,4"}, + expect: []float32{1, 2, 3, 4}, + }, + { + flag: &Float32SliceFlag{ + Name: "numbers", + }, + arguments: []string{"--numbers", "1,2", "--numbers", "3,4"}, + expect: []float32{1, 2, 3, 4}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &Command{ + Name: "mock", + Flags: []Flag{tt.flag}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := cmd.Run(buildTestContext(t), append([]string{"mock"}, tt.arguments...)) + + if tt.expectErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + + for _, name := range tt.flag.Names() { + assert.Equalf(t, tt.expect, cmd.Float32Slice(name), "Float32Slice(%v)", name) + } + }) + } +} + +func TestCommand_Float64Slice(t *testing.T) { + tests := []struct { + name string + flag Flag + arguments []string + expect []float64 + expectErr bool + }{ + { + flag: &Float64SliceFlag{ + Name: "numbers", + }, + arguments: []string{"--numbers", "1,2,3,4"}, + expect: []float64{1, 2, 3, 4}, + }, + { + flag: &Float64SliceFlag{ + Name: "numbers", + }, + arguments: []string{"--numbers", "1,2", "--numbers", "3,4"}, + expect: []float64{1, 2, 3, 4}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &Command{ + Name: "mock", + Flags: []Flag{tt.flag}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := cmd.Run(buildTestContext(t), append([]string{"mock"}, tt.arguments...)) + + if tt.expectErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + + for _, name := range tt.flag.Names() { + assert.Equalf(t, tt.expect, cmd.Float64Slice(name), "Float64Slice(%v)", name) + } + }) + } +} diff --git a/flag_float_test.go b/flag_float_test.go new file mode 100644 index 0000000000..21b8a5dffa --- /dev/null +++ b/flag_float_test.go @@ -0,0 +1,169 @@ +package cli + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_FloatFlag(t *testing.T) { + tests := []struct { + name string + flag Flag + arguments []string + expectedValue float64 + expectErr bool + }{ + { + name: "valid", + flag: &FloatFlag{ + Name: "number", + Aliases: []string{"n"}, + }, + arguments: []string{"--number", "-234567"}, + expectedValue: -234567, + }, + { + name: "invalid", + flag: &FloatFlag{ + Name: "number", + }, + arguments: []string{"--number", "gopher"}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &Command{ + Name: "mock", + Flags: []Flag{tt.flag}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := cmd.Run(buildTestContext(t), append([]string{"mock"}, tt.arguments...)) + + if tt.expectErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + + for _, name := range tt.flag.Names() { + assert.Equal(t, tt.expectedValue, cmd.Float(name)) + } + }) + } +} + +func Test_Float32Flag(t *testing.T) { + tests := []struct { + name string + flag Flag + arguments []string + expectedValue float32 + expectErr bool + }{ + { + name: "valid", + flag: &Float32Flag{ + Name: "number", + Aliases: []string{"n"}, + }, + arguments: []string{"--number", "2147483647"}, + expectedValue: 2147483647, + }, + { + name: "invalid", + flag: &Float32Flag{ + Name: "number", + }, + + arguments: []string{"--number", "gopher"}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &Command{ + Name: "mock", + Flags: []Flag{tt.flag}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := cmd.Run(buildTestContext(t), append([]string{"mock"}, tt.arguments...)) + + if tt.expectErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + + for _, name := range tt.flag.Names() { + assert.Equal(t, tt.expectedValue, cmd.Float32(name)) + } + }) + } +} + +func Test_Float64Flag(t *testing.T) { + tests := []struct { + name string + flag Flag + arguments []string + expectedValue float64 + expectErr bool + }{ + { + name: "valid", + flag: &Float64Flag{ + Name: "number", + Aliases: []string{"n"}, + }, + arguments: []string{"--number", "-2147483648"}, + expectedValue: -2147483648, + }, + { + name: "invalid", + flag: &Float64Flag{ + Name: "number", + }, + arguments: []string{"--number", "gopher"}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := &Command{ + Name: "mock", + Flags: []Flag{tt.flag}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := cmd.Run(buildTestContext(t), append([]string{"mock"}, tt.arguments...)) + + if tt.expectErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + + for _, name := range tt.flag.Names() { + assert.Equal(t, tt.expectedValue, cmd.Float64(name)) + } + }) + } +} diff --git a/flag_test.go b/flag_test.go index 202c56aa3e..2aa2908c0f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -743,7 +743,7 @@ var _ = []struct { }, "env: ENV_VAR, str: -f value\t"}, } -//func TestFlagEnvHinter(t *testing.T) { +// func TestFlagEnvHinter(t *testing.T) { // defer func() { // FlagEnvHinter = withEnvHint // }() @@ -756,7 +756,7 @@ var _ = []struct { // t.Errorf("%q does not match %q", output, test.expected) // } // } -//} +// } var stringSliceFlagTests = []struct { name string @@ -2607,7 +2607,7 @@ func TestTimestampFlagApply_MultipleFormats(t *testing.T) { } func TestTimestampFlagApply_ShortenedLayouts(t *testing.T) { - now := time.Now().UTC() + now := time.Now().In(time.UTC) shortenedLayoutsPrecisions := map[string]time.Duration{ time.Kitchen: time.Minute, @@ -3289,12 +3289,12 @@ func TestFlagsByName(t *testing.T) { func TestNonStringMap(t *testing.T) { type ( - floatMap = MapBase[float64, NoConfig, floatValue] + floatMap = MapBase[float64, NoConfig, floatValue[float64]] ) p := map[string]float64{} - var fv floatValue + var fv floatValue[float64] f := &floatMap{ value: &fv, @@ -3352,10 +3352,11 @@ func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { func TestGenericValue_SatisfiesBoolInterface(t *testing.T) { var f boolFlag = &genericValue{} + var fpv float64 assert.False(t, f.IsBoolFlag()) - fv := floatValue(0) + fv := floatValue[float64]{val: &fpv} f = &genericValue{ val: &fv, } From 055074a6a047b4eafa7f48077bc7e3e4f9669a19 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 27 Apr 2025 01:43:14 +0200 Subject: [PATCH 2/7] chore: generate --- godoc-current.txt | 56 +++++++++++++++++++++++++++++++++++++---- testdata/godoc-v3.x.txt | 56 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/godoc-current.txt b/godoc-current.txt index 4a55270015..8ba7018718 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -26,6 +26,11 @@ application: VARIABLES +var ( + NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue[float64]] + NewFloat32Slice = NewSliceBase[float32, NoConfig, floatValue[float32]] + NewFloat64Slice = NewSliceBase[float64, NoConfig, floatValue[float64]] +) var ( NewIntSlice = NewSliceBase[int, IntegerConfig, intValue[int]] NewInt8Slice = NewSliceBase[int8, IntegerConfig, intValue[int8]] @@ -92,7 +97,6 @@ end {{ range $v := .Completions }}{{ $v }} {{ end }}` -var NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue] var NewStringMap = NewMapBase[string, StringConfig, stringValue] var NewStringSlice = NewSliceBase[string, StringConfig, stringValue] var OsExiter = os.Exit @@ -528,6 +532,28 @@ func (cmd *Command) FlagNames() []string func (cmd *Command) Float(name string) float64 Float looks up the value of a local FloatFlag, returns 0 if not found +func (cmd *Command) Float32(name string) float32 + Float32 looks up the value of a local Float32Flag, returns 0 if not found + +func (c *Command) Float32Arg(name string) float32 + +func (c *Command) Float32Args(name string) []float32 + +func (cmd *Command) Float32Slice(name string) []float32 + Float32Slice looks up the value of a local Float32Slice, returns nil if not + found + +func (cmd *Command) Float64(name string) float64 + Float64 looks up the value of a local Float32Flag, returns 0 if not found + +func (c *Command) Float64Arg(name string) float64 + +func (c *Command) Float64Args(name string) []float64 + +func (cmd *Command) Float64Slice(name string) []float64 + Float64Slice looks up the value of a local Float64SliceFlag, returns nil if + not found + func (c *Command) FloatArg(name string) float64 func (c *Command) FloatArgs(name string) []float64 @@ -1023,13 +1049,33 @@ func (f FlagsByName) Less(i, j int) bool func (f FlagsByName) Swap(i, j int) -type FloatArg = ArgumentBase[float64, NoConfig, floatValue] +type Float32Arg = ArgumentBase[float32, NoConfig, floatValue[float32]] + +type Float32Args = ArgumentsBase[float32, NoConfig, floatValue[float32]] + +type Float32Flag = FlagBase[float32, NoConfig, floatValue[float32]] + +type Float32Slice = SliceBase[float32, NoConfig, floatValue[float32]] + +type Float32SliceFlag = FlagBase[[]float32, NoConfig, Float32Slice] + +type Float64Arg = ArgumentBase[float64, NoConfig, floatValue[float64]] + +type Float64Args = ArgumentsBase[float64, NoConfig, floatValue[float64]] + +type Float64Flag = FlagBase[float64, NoConfig, floatValue[float64]] + +type Float64Slice = SliceBase[float64, NoConfig, floatValue[float64]] + +type Float64SliceFlag = FlagBase[[]float64, NoConfig, Float64Slice] + +type FloatArg = ArgumentBase[float64, NoConfig, floatValue[float64]] -type FloatArgs = ArgumentsBase[float64, NoConfig, floatValue] +type FloatArgs = ArgumentsBase[float64, NoConfig, floatValue[float64]] -type FloatFlag = FlagBase[float64, NoConfig, floatValue] +type FloatFlag = FlagBase[float64, NoConfig, floatValue[float64]] -type FloatSlice = SliceBase[float64, NoConfig, floatValue] +type FloatSlice = SliceBase[float64, NoConfig, floatValue[float64]] type FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice] diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 4a55270015..8ba7018718 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -26,6 +26,11 @@ application: VARIABLES +var ( + NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue[float64]] + NewFloat32Slice = NewSliceBase[float32, NoConfig, floatValue[float32]] + NewFloat64Slice = NewSliceBase[float64, NoConfig, floatValue[float64]] +) var ( NewIntSlice = NewSliceBase[int, IntegerConfig, intValue[int]] NewInt8Slice = NewSliceBase[int8, IntegerConfig, intValue[int8]] @@ -92,7 +97,6 @@ end {{ range $v := .Completions }}{{ $v }} {{ end }}` -var NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue] var NewStringMap = NewMapBase[string, StringConfig, stringValue] var NewStringSlice = NewSliceBase[string, StringConfig, stringValue] var OsExiter = os.Exit @@ -528,6 +532,28 @@ func (cmd *Command) FlagNames() []string func (cmd *Command) Float(name string) float64 Float looks up the value of a local FloatFlag, returns 0 if not found +func (cmd *Command) Float32(name string) float32 + Float32 looks up the value of a local Float32Flag, returns 0 if not found + +func (c *Command) Float32Arg(name string) float32 + +func (c *Command) Float32Args(name string) []float32 + +func (cmd *Command) Float32Slice(name string) []float32 + Float32Slice looks up the value of a local Float32Slice, returns nil if not + found + +func (cmd *Command) Float64(name string) float64 + Float64 looks up the value of a local Float32Flag, returns 0 if not found + +func (c *Command) Float64Arg(name string) float64 + +func (c *Command) Float64Args(name string) []float64 + +func (cmd *Command) Float64Slice(name string) []float64 + Float64Slice looks up the value of a local Float64SliceFlag, returns nil if + not found + func (c *Command) FloatArg(name string) float64 func (c *Command) FloatArgs(name string) []float64 @@ -1023,13 +1049,33 @@ func (f FlagsByName) Less(i, j int) bool func (f FlagsByName) Swap(i, j int) -type FloatArg = ArgumentBase[float64, NoConfig, floatValue] +type Float32Arg = ArgumentBase[float32, NoConfig, floatValue[float32]] + +type Float32Args = ArgumentsBase[float32, NoConfig, floatValue[float32]] + +type Float32Flag = FlagBase[float32, NoConfig, floatValue[float32]] + +type Float32Slice = SliceBase[float32, NoConfig, floatValue[float32]] + +type Float32SliceFlag = FlagBase[[]float32, NoConfig, Float32Slice] + +type Float64Arg = ArgumentBase[float64, NoConfig, floatValue[float64]] + +type Float64Args = ArgumentsBase[float64, NoConfig, floatValue[float64]] + +type Float64Flag = FlagBase[float64, NoConfig, floatValue[float64]] + +type Float64Slice = SliceBase[float64, NoConfig, floatValue[float64]] + +type Float64SliceFlag = FlagBase[[]float64, NoConfig, Float64Slice] + +type FloatArg = ArgumentBase[float64, NoConfig, floatValue[float64]] -type FloatArgs = ArgumentsBase[float64, NoConfig, floatValue] +type FloatArgs = ArgumentsBase[float64, NoConfig, floatValue[float64]] -type FloatFlag = FlagBase[float64, NoConfig, floatValue] +type FloatFlag = FlagBase[float64, NoConfig, floatValue[float64]] -type FloatSlice = SliceBase[float64, NoConfig, floatValue] +type FloatSlice = SliceBase[float64, NoConfig, floatValue[float64]] type FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice] From a7c8c1ee5b306ae877739eaf1ce1f6fefc56f8fa Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 27 Apr 2025 01:52:21 +0200 Subject: [PATCH 3/7] chore: update golangci-lint configuration --- .github/workflows/lint.yml | 20 -------------------- .golangci.yaml | 5 +++++ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 65a9683c30..df83344481 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,26 +14,6 @@ permissions: contents: read jobs: - format: - runs-on: ubuntu-latest - steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: stable - - - name: Set up gofumpt - run: go install mvdan.cc/gofumpt@latest - - - name: Run gofumpt - run: | - non_formatted_files="$(gofumpt -l .)" - echo "$non_formatted_files" - test -z "$non_formatted_files" - golangci-lint: runs-on: ubuntu-latest diff --git a/.golangci.yaml b/.golangci.yaml index 18b70c8b0e..473a221ab9 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,4 +1,9 @@ version: "2" + +formatters: + enable: + - gofumpt + linters: enable: - makezero From f1a2e850ad34e5e54d2d5440d78a4a524d522843 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 27 Apr 2025 02:04:37 +0200 Subject: [PATCH 4/7] tests: add more tests --- args_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++ flag_float_test.go | 7 +++++++ flag_test.go | 20 +++++++++++++++++-- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/args_test.go b/args_test.go index 90f6baf2c6..f7a2545ea1 100644 --- a/args_test.go +++ b/args_test.go @@ -8,6 +8,33 @@ import ( "github.com/stretchr/testify/require" ) +func TestArgsFloatTypes(t *testing.T) { + cmd := buildMinimalTestCommand() + var fval float64 + cmd.Arguments = []Argument{ + &FloatArg{ + Name: "ia", + Destination: &fval, + }, + } + + err := cmd.Run(buildTestContext(t), []string{"foo", "10"}) + r := require.New(t) + r.NoError(err) + r.Equal(float64(10), fval) + r.Equal(float64(10), cmd.FloatArg("ia")) + r.Equal(float64(10), cmd.Float64Arg("ia")) + r.Equal(float32(0), cmd.Float32Arg("ia")) + r.Equal(float64(0), cmd.FloatArg("iab")) + r.Equal(int8(0), cmd.Int8Arg("ia")) + r.Equal(int16(0), cmd.Int16Arg("ia")) + r.Equal(int32(0), cmd.Int32Arg("ia")) + r.Equal(int64(0), cmd.Int64Arg("ia")) + r.Empty(cmd.StringArg("ia")) + + r.Error(cmd.Run(buildTestContext(t), []string{"foo", "a"})) +} + func TestArgsIntTypes(t *testing.T) { cmd := buildMinimalTestCommand() var ival int @@ -34,6 +61,29 @@ func TestArgsIntTypes(t *testing.T) { r.Error(cmd.Run(buildTestContext(t), []string{"foo", "10.0"})) } +func TestArgsFloatSliceTypes(t *testing.T) { + cmd := buildMinimalTestCommand() + var fval []float64 + cmd.Arguments = []Argument{ + &FloatArgs{ + Name: "ia", + Min: 1, + Max: -1, + Destination: &fval, + }, + } + + err := cmd.Run(buildTestContext(t), []string{"foo", "10", "20", "30"}) + r := require.New(t) + r.NoError(err) + r.Equal([]float64{10, 20, 30}, fval) + r.Equal([]float64{10, 20, 30}, cmd.FloatArgs("ia")) + r.Equal([]float64{10, 20, 30}, cmd.Float64Args("ia")) + r.Nil(cmd.Float32Args("ia")) + + r.Error(cmd.Run(buildTestContext(t), []string{"foo", "10", "a"})) +} + func TestArgsIntSliceTypes(t *testing.T) { cmd := buildMinimalTestCommand() var ival []int diff --git a/flag_float_test.go b/flag_float_test.go index 21b8a5dffa..2402580ffa 100644 --- a/flag_float_test.go +++ b/flag_float_test.go @@ -167,3 +167,10 @@ func Test_Float64Flag(t *testing.T) { }) } } + +func Test_floatValue_String(t *testing.T) { + var f float64 = 100 + fv := floatValue[float64]{val: &f} + + assert.Equal(t, "100", fv.String()) +} diff --git a/flag_test.go b/flag_test.go index 2aa2908c0f..f6b0578334 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2607,7 +2607,7 @@ func TestTimestampFlagApply_MultipleFormats(t *testing.T) { } func TestTimestampFlagApply_ShortenedLayouts(t *testing.T) { - now := time.Now().In(time.UTC) + now := time.Now().UTC() shortenedLayoutsPrecisions := map[string]time.Duration{ time.Kitchen: time.Minute, @@ -2726,11 +2726,23 @@ func TestFlagDefaultValue(t *testing.T) { expect: `--flag string [ --flag string ] (default: "default1", "default2")`, }, { - name: "float64Slice", + name: "floatSlice", flag: &FloatSliceFlag{Name: "flag", Value: []float64{1.1, 2.2}}, toParse: []string{"--flag", "13.3"}, expect: `--flag float [ --flag float ] (default: 1.1, 2.2)`, }, + { + name: "float32Slice", + flag: &Float32SliceFlag{Name: "flag", Value: []float32{1.1, 2.2}}, + toParse: []string{"--flag", "13.3"}, + expect: `--flag float [ --flag float ] (default: 1.1, 2.2)`, + }, + { + name: "float64Slice", + flag: &Float64SliceFlag{Name: "flag", Value: []float64{1.1, 2.2}}, + toParse: []string{"--flag", "13.3"}, + expect: `--flag float [ --flag float ] (default: 1.1, 2.2)`, + }, { name: "intSlice", flag: &Int64SliceFlag{Name: "flag", Value: []int64{1, 2}}, @@ -3243,11 +3255,15 @@ func TestExtFlag(t *testing.T) { func TestSliceValuesNil(t *testing.T) { assert.Equal(t, []float64(nil), NewFloatSlice().Value()) + assert.Equal(t, []float32(nil), NewFloat32Slice().Value()) + assert.Equal(t, []float64(nil), NewFloat64Slice().Value()) assert.Equal(t, []int64(nil), NewInt64Slice().Value()) assert.Equal(t, []uint64(nil), NewUint64Slice().Value()) assert.Equal(t, []string(nil), NewStringSlice().Value()) assert.Equal(t, []float64(nil), (&FloatSlice{}).Value()) + assert.Equal(t, []float32(nil), (&Float32Slice{}).Value()) + assert.Equal(t, []float64(nil), (&Float64Slice{}).Value()) assert.Equal(t, []int64(nil), (&Int64Slice{}).Value()) assert.Equal(t, []uint64(nil), (&Uint64Slice{}).Value()) assert.Equal(t, []string(nil), (&StringSlice{}).Value()) From 56720b2d6c41f9e49f3ddb7a1a372a3f5d396737 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 27 Apr 2025 03:12:41 +0200 Subject: [PATCH 5/7] docs: basics --- docs/v3/examples/flags/basics.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/v3/examples/flags/basics.md b/docs/v3/examples/flags/basics.md index 700635dd93..b587ed6835 100644 --- a/docs/v3/examples/flags/basics.md +++ b/docs/v3/examples/flags/basics.md @@ -147,6 +147,8 @@ The following basic flags are supported - `BoolFlag` - `DurationFlag` - `FloatFlag` +- `Float32Flag` +- `Float64Flag` - `StringFlag` - `TimestampFlag` From 98eb9e4e7ab3af62137f47060fe8cd1d009b8f54 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 27 Apr 2025 03:27:26 +0200 Subject: [PATCH 6/7] refactor: merge getIntSlice and getFloatSlice --- flag_float_slice.go | 17 +++------------ flag_int_slice.go | 21 +++++------------- flag_number_slice.go | 15 +++++++++++++ flag_number_slice_test.go | 45 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 flag_number_slice.go create mode 100644 flag_number_slice_test.go diff --git a/flag_float_slice.go b/flag_float_slice.go index 76ea149140..9a6a46d8ac 100644 --- a/flag_float_slice.go +++ b/flag_float_slice.go @@ -18,28 +18,17 @@ var ( // FloatSlice looks up the value of a local FloatSliceFlag, returns // nil if not found func (cmd *Command) FloatSlice(name string) []float64 { - return getFloatSlice[float64](cmd, name) + return getNumberSlice[float64](cmd, name) } // Float32Slice looks up the value of a local Float32Slice, returns // nil if not found func (cmd *Command) Float32Slice(name string) []float32 { - return getFloatSlice[float32](cmd, name) + return getNumberSlice[float32](cmd, name) } // Float64Slice looks up the value of a local Float64SliceFlag, returns // nil if not found func (cmd *Command) Float64Slice(name string) []float64 { - return getFloatSlice[float64](cmd, name) -} - -func getFloatSlice[T float32 | float64](cmd *Command, name string) []T { - if v, ok := cmd.Value(name).([]T); ok { - tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name) - - return v - } - - tracef("float slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name) - return nil + return getNumberSlice[float64](cmd, name) } diff --git a/flag_int_slice.go b/flag_int_slice.go index 24d53a46fc..22dcb5a24c 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -24,40 +24,29 @@ var ( // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (cmd *Command) IntSlice(name string) []int { - return getIntSlice[int](cmd, name) + return getNumberSlice[int](cmd, name) } // Int8Slice looks up the value of a local Int8SliceFlag, returns // nil if not found func (cmd *Command) Int8Slice(name string) []int8 { - return getIntSlice[int8](cmd, name) + return getNumberSlice[int8](cmd, name) } // Int16Slice looks up the value of a local Int16SliceFlag, returns // nil if not found func (cmd *Command) Int16Slice(name string) []int16 { - return getIntSlice[int16](cmd, name) + return getNumberSlice[int16](cmd, name) } // Int32Slice looks up the value of a local Int32SliceFlag, returns // nil if not found func (cmd *Command) Int32Slice(name string) []int32 { - return getIntSlice[int32](cmd, name) + return getNumberSlice[int32](cmd, name) } // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (cmd *Command) Int64Slice(name string) []int64 { - return getIntSlice[int64](cmd, name) -} - -func getIntSlice[T int | int8 | int16 | int32 | int64](cmd *Command, name string) []T { - if v, ok := cmd.Value(name).([]T); ok { - tracef("int slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name) - - return v - } - - tracef("int slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name) - return nil + return getNumberSlice[int64](cmd, name) } diff --git a/flag_number_slice.go b/flag_number_slice.go new file mode 100644 index 0000000000..77e317020e --- /dev/null +++ b/flag_number_slice.go @@ -0,0 +1,15 @@ +package cli + +type numberType interface { + int | int8 | int16 | int32 | int64 | float32 | float64 +} + +func getNumberSlice[T numberType](cmd *Command, name string) []T { + if v, ok := cmd.Value(name).([]T); ok { + tracef("%T slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", *new(T), name, v, cmd.Name) + return v + } + + tracef("%T slice NOT available for flag name %[1]q (cmd=%[2]q)", *new(T), name, cmd.Name) + return nil +} diff --git a/flag_number_slice_test.go b/flag_number_slice_test.go new file mode 100644 index 0000000000..72572c052a --- /dev/null +++ b/flag_number_slice_test.go @@ -0,0 +1,45 @@ +package cli + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_getNumberSlice_int64(t *testing.T) { + f := &Int64SliceFlag{Name: "numbers"} + + cmd := &Command{ + Name: "mock", + Flags: []Flag{f}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := f.Set("", "1,2,3") + require.NoError(t, err) + + expected := []int64{1, 2, 3} + + assert.Equal(t, expected, getNumberSlice[int64](cmd, "numbers")) +} + +func Test_getNumberSlice_float64(t *testing.T) { + f := &Float64SliceFlag{Name: "numbers"} + + cmd := &Command{ + Name: "mock", + Flags: []Flag{f}, + Writer: io.Discard, + ErrWriter: io.Discard, + } + + err := f.Set("", "1,2,3") + require.NoError(t, err) + + expected := []float64{1, 2, 3} + + assert.Equal(t, expected, getNumberSlice[float64](cmd, "numbers")) +} From 55836e631959b42fb74d366f5d2f0acb4ef7b7ce Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 27 Apr 2025 03:31:17 +0200 Subject: [PATCH 7/7] docs: fix typos --- flag_float.go | 2 +- godoc-current.txt | 2 +- testdata/godoc-v3.x.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flag_float.go b/flag_float.go index da4555e895..71aa0c27b9 100644 --- a/flag_float.go +++ b/flag_float.go @@ -57,7 +57,7 @@ func (cmd *Command) Float32(name string) float32 { return getFloat[float32](cmd, name) } -// Float64 looks up the value of a local Float32Flag, returns +// Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (cmd *Command) Float64(name string) float64 { return getFloat[float64](cmd, name) diff --git a/godoc-current.txt b/godoc-current.txt index 8ba7018718..3b5f8938d9 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -544,7 +544,7 @@ func (cmd *Command) Float32Slice(name string) []float32 found func (cmd *Command) Float64(name string) float64 - Float64 looks up the value of a local Float32Flag, returns 0 if not found + Float64 looks up the value of a local Float64Flag, returns 0 if not found func (c *Command) Float64Arg(name string) float64 diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 8ba7018718..3b5f8938d9 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -544,7 +544,7 @@ func (cmd *Command) Float32Slice(name string) []float32 found func (cmd *Command) Float64(name string) float64 - Float64 looks up the value of a local Float32Flag, returns 0 if not found + Float64 looks up the value of a local Float64Flag, returns 0 if not found func (c *Command) Float64Arg(name string) float64