diff --git a/command_test.go b/command_test.go index d57cc26..03b32b5 100644 --- a/command_test.go +++ b/command_test.go @@ -600,6 +600,8 @@ func TestHelp(t *testing.T) { cli.SubCommands(sub1, sub2), cli.Flag(new(bool), "delete", 'd', false, "Delete something"), cli.Flag(new(int), "count", cli.NoShortHand, -1, "Count something"), + cli.Flag(new([]string), "things", cli.NoShortHand, nil, "Names of things"), + cli.Flag(new([]string), "more", cli.NoShortHand, []string{"one", "two"}, "Names of things with a default"), }, wantErr: false, }, diff --git a/go.sum b/go.sum index ef09ba7..27af395 100644 --- a/go.sum +++ b/go.sum @@ -2,17 +2,11 @@ go.followtheprocess.codes/hue v1.0.0 h1:0fYXAGR1o+w7Vja+Q+iVtqeEP3/CE6ET/pniyl8e go.followtheprocess.codes/hue v1.0.0/go.mod h1:gSn5xK6KJapih+eFgQk3woo1qg3/rx9XSrAanUIuDr8= go.followtheprocess.codes/snapshot v0.6.1 h1:cZkQtEjL21BSrHn98Lm0S4yTzcOje/K60Iog/u/A5tM= go.followtheprocess.codes/snapshot v0.6.1/go.mod h1:CM2E92Ah/j0XL4Z2UyOl7GlSuD0ZLLl8rJCpFylKcIg= -go.followtheprocess.codes/test v0.23.1 h1:VoucCC8qKb6tKnBOCRZ7Ln2Ex1oV+HMXHdZyJ6DURB8= -go.followtheprocess.codes/test v0.23.1/go.mod h1:sL19ttHv+BSJSMtdPXTP05aWwl58ciap0SoLp1hucKk= go.followtheprocess.codes/test v1.0.0 h1:5m2MPOQpohDC9pf5hgqpH+4ldJP5g+YFVdoGQY41aeU= go.followtheprocess.codes/test v1.0.0/go.mod h1:e627pR8IhsTV/RfuP/WKYjyL0BmuIbmaw2iKlQBCWrY= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= diff --git a/internal/flag/flag.go b/internal/flag/flag.go index f0e8a60..6992075 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -107,7 +107,7 @@ func New[T Flaggable](p *T, name string, short rune, value T, usage string) (Fla // If the default value is not the zero value for the type, it is treated as // significant and shown to the user - if !isZero(value) { + if !isZeroIsh(value) { // \t so that defaults get aligned by tabwriter when the command // dumps the flags usage += fmt.Sprintf("\t[default: %v]", value) @@ -905,8 +905,16 @@ func formatStringSlice(slice []string) string { return s.String() } -// isZero reports whether value is the zero value for it's type. -func isZero[T Flaggable](value T) bool { +// isZeroIsh reports whether value is the zero value (ish) for it's type. +// +// "ish" means that empty slices will return true from isZeroIsh despite their official +// zero value being nil. The primary use of isZeroIsh is to determine whether or not +// a default value is worth displaying to the user in the help text, and an empty slice +// is probably not. +func isZeroIsh[T Flaggable](value T) bool { //nolint:cyclop // Not much else we can do here + // Note: all the slice values ([]T) are in their own separate branches because if you + // combine them, the resulting value in the body of the case block is 'any' and + // you cannot do len(any) switch typ := any(value).(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64: return typ == 0 @@ -916,8 +924,34 @@ func isZero[T Flaggable](value T) bool { return typ == "" case bool: return !typ - case []byte, net.IP, []int, []int8, []int16, []int32, []int64, []uint, []uint16, []uint32, []uint64, []float32, []float64, []string: - return typ == nil + case []byte: + return len(typ) == 0 + case net.IP: + return len(typ) == 0 + case []int: + return len(typ) == 0 + case []int8: + return len(typ) == 0 + case []int16: + return len(typ) == 0 + case []int32: + return len(typ) == 0 + case []int64: + return len(typ) == 0 + case []uint: + return len(typ) == 0 + case []uint16: + return len(typ) == 0 + case []uint32: + return len(typ) == 0 + case []uint64: + return len(typ) == 0 + case []float32: + return len(typ) == 0 + case []float64: + return len(typ) == 0 + case []string: + return len(typ) == 0 case time.Time: var zero time.Time return typ.Equal(zero) diff --git a/testdata/snapshots/TestHelp/with_subcommands_and_flags.snap.txt b/testdata/snapshots/TestHelp/with_subcommands_and_flags.snap.txt index 0a335ed..7c8c36d 100644 --- a/testdata/snapshots/TestHelp/with_subcommands_and_flags.snap.txt +++ b/testdata/snapshots/TestHelp/with_subcommands_and_flags.snap.txt @@ -11,9 +11,11 @@ Commands: Options: - N/A --count int Count something [default: -1] - -d --delete bool Delete something - -h --help bool Show help for test - -V --version bool Show version info for test + N/A --count int Count something [default: -1] + -d --delete bool Delete something + -h --help bool Show help for test + N/A --more []string Names of things with a default [default: [one two]] + N/A --things []string Names of things + -V --version bool Show version info for test Use "test [command] -h/--help" for more information about a command.