diff --git a/args_test.go b/args_test.go index f7a2545ea1..f87af88e18 100644 --- a/args_test.go +++ b/args_test.go @@ -8,6 +8,15 @@ import ( "github.com/stretchr/testify/require" ) +func TestArgNotSet(t *testing.T) { + arg := &StringArg{ + Name: "sa", + Value: "foo", + } + + require.Equal(t, "foo", arg.Get()) +} + func TestArgsFloatTypes(t *testing.T) { cmd := buildMinimalTestCommand() var fval float64 diff --git a/command.go b/command.go index 2572c36374..c369c0191c 100644 --- a/command.go +++ b/command.go @@ -334,14 +334,10 @@ func (cmd *Command) handleExitCoder(ctx context.Context, err error) error { } func (cmd *Command) argsWithDefaultCommand(oldArgs Args) Args { - if cmd.DefaultCommand != "" { - rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...) - newArgs := &stringSliceArgs{v: rawArgs} + rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...) + newArgs := &stringSliceArgs{v: rawArgs} - return newArgs - } - - return oldArgs + return newArgs } // Root returns the Command at the root of the graph diff --git a/flag.go b/flag.go index 6ff83f5207..bfac8faaf6 100644 --- a/flag.go +++ b/flag.go @@ -77,11 +77,6 @@ func (f FlagsByName) Len() int { } func (f FlagsByName) Less(i, j int) bool { - if len(f[j].Names()) == 0 { - return false - } else if len(f[i].Names()) == 0 { - return true - } return lexicographicLess(f[i].Names()[0], f[j].Names()[0]) } diff --git a/flag_bool.go b/flag_bool.go index d576448240..9019bea2bf 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -50,7 +50,8 @@ func (b boolValue) Create(val bool, p *bool, c BoolConfig) Value { // ToString formats the bool value func (b boolValue) ToString(value bool) string { - return strconv.FormatBool(value) + b.destination = &value + return b.String() } // Below functions are to satisfy the flag.Value interface diff --git a/flag_duration.go b/flag_duration.go index 37b4cb642f..e14ff428fc 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -18,7 +18,8 @@ func (d durationValue) Create(val time.Duration, p *time.Duration, c NoConfig) V } func (d durationValue) ToString(val time.Duration) string { - return fmt.Sprintf("%v", val) + d = durationValue(val) + return d.String() } // Below functions are to satisfy the flag.Value interface @@ -34,7 +35,9 @@ func (d *durationValue) Set(s string) error { func (d *durationValue) Get() any { return time.Duration(*d) } -func (d *durationValue) String() string { return (*time.Duration)(d).String() } +func (d *durationValue) String() string { + return fmt.Sprintf("%v", time.Duration(*d)) +} func (cmd *Command) Duration(name string) time.Duration { if v, ok := cmd.Value(name).(time.Duration); ok { diff --git a/flag_float.go b/flag_float.go index 71aa0c27b9..6173e80dea 100644 --- a/flag_float.go +++ b/flag_float.go @@ -25,7 +25,8 @@ func (f floatValue[T]) Create(val T, p *T, c NoConfig) Value { } func (f floatValue[T]) ToString(b T) string { - return strconv.FormatFloat(float64(b), 'g', -1, int(unsafe.Sizeof(T(0))*8)) + f.val = &b + return f.String() } // Below functions are to satisfy the flag.Value interface diff --git a/flag_generic.go b/flag_generic.go index 9618409ee8..5ee07c7a36 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -17,10 +17,8 @@ func (f genericValue) Create(val Value, p *Value, c NoConfig) Value { } func (f genericValue) ToString(b Value) string { - if b != nil { - return b.String() - } - return "" + f.val = b + return f.String() } // Below functions are to satisfy the flag.Value interface diff --git a/flag_int.go b/flag_int.go index 0e082221e4..8e5af17092 100644 --- a/flag_int.go +++ b/flag_int.go @@ -36,11 +36,8 @@ func (i intValue[T]) Create(val T, p *T, c IntegerConfig) Value { } func (i intValue[T]) ToString(b T) string { - if i.base == 0 { - i.base = 10 - } - - return strconv.FormatInt(int64(b), i.base) + i.val = &b + return i.String() } // Below functions are to satisfy the flag.Value interface diff --git a/flag_slice_base.go b/flag_slice_base.go index f1c9d0ba88..0248d8f1dc 100644 --- a/flag_slice_base.go +++ b/flag_slice_base.go @@ -82,12 +82,12 @@ func (i *SliceBase[T, C, VC]) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (i *SliceBase[T, C, VC]) String() string { - v := i.Value() - var t T - if reflect.TypeOf(t).Kind() == reflect.String { - return fmt.Sprintf("%v", v) + var defaultVals []string + var v VC + for _, s := range *i.slice { + defaultVals = append(defaultVals, v.ToString(s)) } - return fmt.Sprintf("%T{%s}", v, i.ToString(v)) + return strings.Join(defaultVals, ", ") } // Serialize allows SliceBase to fulfill Serializer @@ -110,10 +110,6 @@ func (i *SliceBase[T, C, VC]) Get() interface{} { } func (i SliceBase[T, C, VC]) ToString(t []T) string { - var defaultVals []string - var v VC - for _, s := range t { - defaultVals = append(defaultVals, v.ToString(s)) - } - return strings.Join(defaultVals, ", ") + i.slice = &t + return i.String() } diff --git a/flag_string.go b/flag_string.go index bdc1ec65fc..ca24b379cc 100644 --- a/flag_string.go +++ b/flag_string.go @@ -30,10 +30,8 @@ func (s stringValue) Create(val string, p *string, c StringConfig) Value { } func (s stringValue) ToString(val string) string { - if val == "" { - return val - } - return fmt.Sprintf("%q", val) + s.destination = &val + return s.String() } // Below functions are to satisfy the flag.Value interface @@ -49,8 +47,8 @@ func (s *stringValue) Set(val string) error { func (s *stringValue) Get() any { return *s.destination } func (s *stringValue) String() string { - if s.destination != nil { - return *s.destination + if s.destination != nil && *s.destination != "" { + return fmt.Sprintf("%q", *s.destination) } return "" } diff --git a/flag_test.go b/flag_test.go index 4fd1890205..25b0b21e1c 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1749,6 +1749,29 @@ func TestFlagActionFromEnv(t *testing.T) { assert.Equal(t, x, 42) } +func TestParseShortOptionBoolError(t *testing.T) { + cmd := buildMinimalTestCommand() + cmd.UseShortOptionHandling = true + cmd.Flags = []Flag{ + &BoolFlag{Name: "debug", Aliases: []string{"d"}}, + &BoolFlag{Name: "verbose", Aliases: []string{"v"}}, + } + + err := cmd.Run(buildTestContext(t), []string{"run", "-vd=notabool"}) + assert.Error(t, err, "expected error parsing invalid bool") +} + +func TestParseShortOptionIntError(t *testing.T) { + cmd := buildMinimalTestCommand() + cmd.Flags = []Flag{ + &IntFlag{Name: "port", Aliases: []string{"p"}}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}}, + } + + err := cmd.Run(buildTestContext(t), []string{"run", "-dp=notanint"}) + assert.Error(t, err, "expected error parsing invalid int") +} + func TestParseMultiString(t *testing.T) { _ = (&Command{ Flags: []Flag{ @@ -3294,6 +3317,17 @@ func TestFileHint(t *testing.T) { assert.Equal(t, "bar [/tmp/foo.txt]", withFileHint("/tmp/foo.txt", "bar")) } +func TestHasFlags(t *testing.T) { + flagToCheck := &StringFlag{Name: "foo"} + flags := []Flag{ + &StringFlag{Name: "bar"}, + &Int64Flag{Name: "baz"}, + flagToCheck, + } + + assert.True(t, hasFlag(flags, flagToCheck)) +} + func TestFlagsByName(t *testing.T) { flags := []Flag{ &StringFlag{ diff --git a/flag_timestamp.go b/flag_timestamp.go index 413a2f0e48..82b1cb5cf0 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -44,7 +44,8 @@ func (t timestampValue) ToString(b time.Time) string { if b.IsZero() { return "" } - return fmt.Sprintf("%v", b) + t.timestamp = &b + return t.String() } // Below functions are to satisfy the Value interface @@ -122,7 +123,7 @@ func (t *timestampValue) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (t *timestampValue) String() string { - return fmt.Sprintf("%#v", t.timestamp) + return fmt.Sprintf("%v", t.timestamp) } // Get returns the flag structure diff --git a/flag_uint.go b/flag_uint.go index 64ee231926..9818579482 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -31,12 +31,8 @@ func (i uintValue[T]) Create(val T, p *T, c IntegerConfig) Value { } func (i uintValue[T]) ToString(b T) string { - base := i.base - if base == 0 { - base = 10 - } - - return strconv.FormatUint(uint64(b), base) + i.val = &b + return i.String() } // Below functions are to satisfy the flag.Value interface