diff --git a/flag.go b/flag.go index c3a96b3018..063f9c4175 100644 --- a/flag.go +++ b/flag.go @@ -182,7 +182,7 @@ type LocalFlag interface { // FlagType is an interface to detect if a flag is a string, bool, etc. type FlagType interface { - GetFlagType() string + TypeName() string } func newFlagSet(name string, flags []Flag) (*flag.FlagSet, error) { @@ -312,8 +312,8 @@ func stringifyFlag(f Flag) string { // if needsPlaceholder is true, placeholder is empty if needsPlaceholder && placeholder == "" { // try to get type from flag - if v1, ok := f.(FlagType); ok && v1.GetFlagType() != "" { - placeholder = v1.GetFlagType() + if ft, ok := f.(FlagType); ok && ft.TypeName() != "" { + placeholder = ft.TypeName() } else { placeholder = defaultPlaceholder } diff --git a/flag_impl.go b/flag_impl.go index bf294b00c9..152dc1fef8 100644 --- a/flag_impl.go +++ b/flag_impl.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "reflect" + "strings" ) // Value represents a value as used by cli. @@ -98,18 +99,40 @@ func (f *FlagBase[T, C, V]) GetValue() string { return fmt.Sprintf("%v", f.Value) } -// GetFlagType returns the type of the flag. -func (f *FlagBase[T, C, V]) GetFlagType() string { +// TypeName returns the type of the flag. +func (f *FlagBase[T, C, V]) TypeName() string { ty := reflect.TypeOf(f.Value) if ty == nil { return "" } + // convert the typename to generic type + convertToGenericType := func(name string) string { + prefixMap := map[string]string{ + "float": "float", + "int": "int", + "uint": "uint", + } + for prefix, genericType := range prefixMap { + if strings.HasPrefix(name, prefix) { + return genericType + } + } + return strings.ToLower(name) + } + + switch ty.Kind() { // if it is a Slice, then return the slice's inner type. Will nested slices be used in the future? - if ty.Kind() == reflect.Slice { + case reflect.Slice: elemType := ty.Elem() - return elemType.Name() + return convertToGenericType(elemType.Name()) + // if it is a Map, then return the map's key and value types. + case reflect.Map: + keyType := ty.Key() + valueType := ty.Elem() + return fmt.Sprintf("%s=%s", convertToGenericType(keyType.Name()), convertToGenericType(valueType.Name())) + default: + return convertToGenericType(ty.Name()) } - return ty.Name() } // Apply populates the flag given the flag set and environment diff --git a/flag_test.go b/flag_test.go index 0414e4e154..7246c209c0 100644 --- a/flag_test.go +++ b/flag_test.go @@ -439,32 +439,32 @@ func TestFlagStringifying(t *testing.T) { { name: "duration-flag", fl: &DurationFlag{Name: "scream-for"}, - expected: "--scream-for Duration\t(default: 0s)", + expected: "--scream-for duration\t(default: 0s)", }, { name: "duration-flag-with-default-text", fl: &DurationFlag{Name: "feels-about", DefaultText: "whimsically"}, - expected: "--feels-about Duration\t(default: whimsically)", + expected: "--feels-about duration\t(default: whimsically)", }, { name: "float64-flag", fl: &FloatFlag{Name: "arduous"}, - expected: "--arduous float64\t(default: 0)", + expected: "--arduous float\t(default: 0)", }, { name: "float64-flag-with-default-text", fl: &FloatFlag{Name: "filibuster", DefaultText: "42"}, - expected: "--filibuster float64\t(default: 42)", + expected: "--filibuster float\t(default: 42)", }, { name: "float64-slice-flag", fl: &FloatSliceFlag{Name: "pizzas"}, - expected: "--pizzas float64 [ --pizzas float64 ]\t", + expected: "--pizzas float [ --pizzas float ]\t", }, { name: "float64-slice-flag-with-default-text", fl: &FloatSliceFlag{Name: "pepperonis", DefaultText: "shaved"}, - expected: "--pepperonis float64 [ --pepperonis float64 ]\t(default: shaved)", + expected: "--pepperonis float [ --pepperonis float ]\t(default: shaved)", }, { name: "generic-flag", @@ -479,52 +479,52 @@ func TestFlagStringifying(t *testing.T) { { name: "int-flag", fl: &IntFlag{Name: "grubs"}, - expected: "--grubs int64\t(default: 0)", + expected: "--grubs int\t(default: 0)", }, { name: "int-flag-with-default-text", fl: &IntFlag{Name: "poisons", DefaultText: "11ty"}, - expected: "--poisons int64\t(default: 11ty)", + expected: "--poisons int\t(default: 11ty)", }, { name: "int-slice-flag", fl: &IntSliceFlag{Name: "pencils"}, - expected: "--pencils int64 [ --pencils int64 ]\t", + expected: "--pencils int [ --pencils int ]\t", }, { name: "int-slice-flag-with-default-text", fl: &IntFlag{Name: "pens", DefaultText: "-19"}, - expected: "--pens int64\t(default: -19)", + expected: "--pens int\t(default: -19)", }, { name: "uint-slice-flag", fl: &UintSliceFlag{Name: "pencils"}, - expected: "--pencils uint64 [ --pencils uint64 ]\t", + expected: "--pencils uint [ --pencils uint ]\t", }, { name: "uint-slice-flag-with-default-text", fl: &UintFlag{Name: "pens", DefaultText: "29"}, - expected: "--pens uint64\t(default: 29)", + expected: "--pens uint\t(default: 29)", }, { name: "int64-flag", fl: &IntFlag{Name: "flume"}, - expected: "--flume int64\t(default: 0)", + expected: "--flume int\t(default: 0)", }, { name: "int64-flag-with-default-text", fl: &IntFlag{Name: "shattering", DefaultText: "22"}, - expected: "--shattering int64\t(default: 22)", + expected: "--shattering int\t(default: 22)", }, { name: "uint64-slice-flag", fl: &UintSliceFlag{Name: "drawers"}, - expected: "--drawers uint64 [ --drawers uint64 ]\t", + expected: "--drawers uint [ --drawers uint ]\t", }, { name: "uint64-slice-flag-with-default-text", fl: &UintSliceFlag{Name: "handles", DefaultText: "-2"}, - expected: "--handles uint64 [ --handles uint64 ]\t(default: -2)", + expected: "--handles uint [ --handles uint ]\t(default: -2)", }, { name: "string-flag", @@ -549,32 +549,32 @@ func TestFlagStringifying(t *testing.T) { { name: "timestamp-flag", fl: &TimestampFlag{Name: "eating"}, - expected: "--eating Time\t", + expected: "--eating time\t", }, { name: "timestamp-flag-with-default-text", fl: &TimestampFlag{Name: "sleeping", DefaultText: "earlier"}, - expected: "--sleeping Time\t(default: earlier)", + expected: "--sleeping time\t(default: earlier)", }, { name: "uint-flag", fl: &UintFlag{Name: "jars"}, - expected: "--jars uint64\t(default: 0)", + expected: "--jars uint\t(default: 0)", }, { name: "uint-flag-with-default-text", fl: &UintFlag{Name: "bottles", DefaultText: "99"}, - expected: "--bottles uint64\t(default: 99)", + expected: "--bottles uint\t(default: 99)", }, { name: "uint64-flag", fl: &UintFlag{Name: "cans"}, - expected: "--cans uint64\t(default: 0)", + expected: "--cans uint\t(default: 0)", }, { name: "uint64-flag-with-default-text", fl: &UintFlag{Name: "tubes", DefaultText: "13"}, - expected: "--tubes uint64\t(default: 13)", + expected: "--tubes uint\t(default: 13)", }, { name: "nodoc-flag", @@ -814,8 +814,8 @@ var intFlagTests = []struct { name string expected string }{ - {"hats", "--hats int64\t(default: 9)"}, - {"H", "-H int64\t(default: 9)"}, + {"hats", "--hats int\t(default: 9)"}, + {"H", "-H int\t(default: 9)"}, } func TestIntFlagHelpOutput(t *testing.T) { @@ -868,8 +868,8 @@ var uintFlagTests = []struct { name string expected string }{ - {"nerfs", "--nerfs uint64\t(default: 41)"}, - {"N", "-N uint64\t(default: 41)"}, + {"nerfs", "--nerfs uint\t(default: 41)"}, + {"N", "-N uint\t(default: 41)"}, } func TestUintFlagHelpOutput(t *testing.T) { @@ -911,8 +911,8 @@ var uint64FlagTests = []struct { name string expected string }{ - {"gerfs", "--gerfs uint64\t(default: 8589934582)"}, - {"G", "-G uint64\t(default: 8589934582)"}, + {"gerfs", "--gerfs uint\t(default: 8589934582)"}, + {"G", "-G uint\t(default: 8589934582)"}, } func TestUint64FlagHelpOutput(t *testing.T) { @@ -954,8 +954,8 @@ var durationFlagTests = []struct { name string expected string }{ - {"hooting", "--hooting Duration\t(default: 1s)"}, - {"H", "-H Duration\t(default: 1s)"}, + {"hooting", "--hooting duration\t(default: 1s)"}, + {"H", "-H duration\t(default: 1s)"}, } func TestDurationFlagHelpOutput(t *testing.T) { @@ -1010,9 +1010,9 @@ var intSliceFlagTests = []struct { value []int64 expected string }{ - {"heads", nil, []int64{}, "--heads int64 [ --heads int64 ]\t"}, - {"H", nil, []int64{}, "-H int64 [ -H int64 ]\t"}, - {"H", []string{"heads"}, []int64{9, 3}, "-H int64, --heads int64 [ -H int64, --heads int64 ]\t(default: 9, 3)"}, + {"heads", nil, []int64{}, "--heads int [ --heads int ]\t"}, + {"H", nil, []int64{}, "-H int [ -H int ]\t"}, + {"H", []string{"heads"}, []int64{9, 3}, "-H int, --heads int [ -H int, --heads int ]\t(default: 9, 3)"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { @@ -1131,13 +1131,13 @@ var uintSliceFlagTests = []struct { value []uint64 expected string }{ - {"heads", nil, []uint64{}, "--heads uint64 [ --heads uint64 ]\t"}, - {"H", nil, []uint64{}, "-H uint64 [ -H uint64 ]\t"}, + {"heads", nil, []uint64{}, "--heads uint [ --heads uint ]\t"}, + {"H", nil, []uint64{}, "-H uint [ -H uint ]\t"}, { "heads", []string{"H"}, []uint64{2, 17179869184}, - "--heads uint64, -H uint64 [ --heads uint64, -H uint64 ]\t(default: 2, 17179869184)", + "--heads uint, -H uint [ --heads uint, -H uint ]\t(default: 2, 17179869184)", }, } @@ -1276,13 +1276,13 @@ var uint64SliceFlagTests = []struct { value []uint64 expected string }{ - {"heads", nil, []uint64{}, "--heads uint64 [ --heads uint64 ]\t"}, - {"H", nil, []uint64{}, "-H uint64 [ -H uint64 ]\t"}, + {"heads", nil, []uint64{}, "--heads uint [ --heads uint ]\t"}, + {"H", nil, []uint64{}, "-H uint [ -H uint ]\t"}, { "heads", []string{"H"}, []uint64{2, 17179869184}, - "--heads uint64, -H uint64 [ --heads uint64, -H uint64 ]\t(default: 2, 17179869184)", + "--heads uint, -H uint [ --heads uint, -H uint ]\t(default: 2, 17179869184)", }, } @@ -1415,8 +1415,8 @@ var float64FlagTests = []struct { name string expected string }{ - {"hooting", "--hooting float64\t(default: 0.1)"}, - {"H", "-H float64\t(default: 0.1)"}, + {"hooting", "--hooting float\t(default: 0.1)"}, + {"H", "-H float\t(default: 0.1)"}, } func TestFloat64FlagHelpOutput(t *testing.T) { @@ -1467,13 +1467,13 @@ var float64SliceFlagTests = []struct { value []float64 expected string }{ - {"heads", nil, []float64{}, "--heads float64 [ --heads float64 ]\t"}, - {"H", nil, []float64{}, "-H float64 [ -H float64 ]\t"}, + {"heads", nil, []float64{}, "--heads float [ --heads float ]\t"}, + {"H", nil, []float64{}, "-H float [ -H float ]\t"}, { "heads", []string{"H"}, []float64{0.1234, -10.5}, - "--heads float64, -H float64 [ --heads float64, -H float64 ]\t(default: 0.1234, -10.5)", + "--heads float, -H float [ --heads float, -H float ]\t(default: 0.1234, -10.5)", }, } @@ -2670,19 +2670,19 @@ func TestFlagDefaultValue(t *testing.T) { name: "float64Slice", flag: &FloatSliceFlag{Name: "flag", Value: []float64{1.1, 2.2}}, toParse: []string{"--flag", "13.3"}, - expect: `--flag float64 [ --flag float64 ] (default: 1.1, 2.2)`, + expect: `--flag float [ --flag float ] (default: 1.1, 2.2)`, }, { name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: []int64{1, 2}}, toParse: []string{"--flag", "13"}, - expect: `--flag int64 [ --flag int64 ] (default: 1, 2)`, + expect: `--flag int [ --flag int ] (default: 1, 2)`, }, { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: []uint64{1, 2}}, toParse: []string{"--flag", "13"}, - expect: `--flag uint64 [ --flag uint64 ] (default: 1, 2)`, + expect: `--flag uint [ --flag uint ] (default: 1, 2)`, }, { name: "string", @@ -2700,13 +2700,13 @@ func TestFlagDefaultValue(t *testing.T) { name: "uint64", flag: &UintFlag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag uint64 (default: 1)`, + expect: `--flag uint (default: 1)`, }, { name: "stringMap", flag: &StringMapFlag{Name: "flag", Value: map[string]string{"default1": "default2"}}, toParse: []string{"--flag", "parsed="}, - expect: `--flag value [ --flag value ] (default: default1="default2")`, + expect: `--flag string=string [ --flag string=string ] (default: default1="default2")`, }, } for _, v := range cases { @@ -2746,7 +2746,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "float64Slice", flag: &FloatSliceFlag{Name: "flag", Value: []float64{1.1, 2.2}, Sources: EnvVars("fsflag")}, toParse: []string{"--flag", "13.3"}, - expect: `--flag float64 [ --flag float64 ] (default: 1.1, 2.2)` + withEnvHint([]string{"fsflag"}, ""), + expect: `--flag float [ --flag float ] (default: 1.1, 2.2)` + withEnvHint([]string{"fsflag"}, ""), environ: map[string]string{ "fsflag": "20304.222", }, @@ -2755,7 +2755,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: []int64{1, 2}, Sources: EnvVars("isflag")}, toParse: []string{"--flag", "13"}, - expect: `--flag int64 [ --flag int64 ] (default: 1, 2)` + withEnvHint([]string{"isflag"}, ""), + expect: `--flag int [ --flag int ] (default: 1, 2)` + withEnvHint([]string{"isflag"}, ""), environ: map[string]string{ "isflag": "101", }, @@ -2764,7 +2764,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: []uint64{1, 2}, Sources: EnvVars("uisflag")}, toParse: []string{"--flag", "13"}, - expect: `--flag uint64 [ --flag uint64 ] (default: 1, 2)` + withEnvHint([]string{"uisflag"}, ""), + expect: `--flag uint [ --flag uint ] (default: 1, 2)` + withEnvHint([]string{"uisflag"}, ""), environ: map[string]string{ "uisflag": "3", }, @@ -2791,7 +2791,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "uint64", flag: &UintFlag{Name: "flag", Value: 1, Sources: EnvVars("uflag")}, toParse: []string{"--flag", "13"}, - expect: `--flag uint64 (default: 1)` + withEnvHint([]string{"uflag"}, ""), + expect: `--flag uint (default: 1)` + withEnvHint([]string{"uflag"}, ""), environ: map[string]string{ "uflag": "10", }, @@ -2800,7 +2800,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "uint", flag: &UintFlag{Name: "flag", Value: 1, Sources: EnvVars("uflag")}, toParse: []string{"--flag", "13"}, - expect: `--flag uint64 (default: 1)` + withEnvHint([]string{"uflag"}, ""), + expect: `--flag uint (default: 1)` + withEnvHint([]string{"uflag"}, ""), environ: map[string]string{ "uflag": "10", }, @@ -2809,7 +2809,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "int64", flag: &IntFlag{Name: "flag", Value: 1, Sources: EnvVars("uflag")}, toParse: []string{"--flag", "13"}, - expect: `--flag int64 (default: 1)` + withEnvHint([]string{"uflag"}, ""), + expect: `--flag int (default: 1)` + withEnvHint([]string{"uflag"}, ""), environ: map[string]string{ "uflag": "10", }, @@ -2818,7 +2818,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "int", flag: &IntFlag{Name: "flag", Value: 1, Sources: EnvVars("uflag")}, toParse: []string{"--flag", "13"}, - expect: `--flag int64 (default: 1)` + withEnvHint([]string{"uflag"}, ""), + expect: `--flag int (default: 1)` + withEnvHint([]string{"uflag"}, ""), environ: map[string]string{ "uflag": "10", }, @@ -2827,7 +2827,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "duration", flag: &DurationFlag{Name: "flag", Value: time.Second, Sources: EnvVars("uflag")}, toParse: []string{"--flag", "2m"}, - expect: `--flag Duration (default: 1s)` + withEnvHint([]string{"uflag"}, ""), + expect: `--flag duration (default: 1s)` + withEnvHint([]string{"uflag"}, ""), environ: map[string]string{ "uflag": "2h4m10s", }, @@ -2836,7 +2836,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "timestamp", flag: &TimestampFlag{Name: "flag", Value: ts, Config: TimestampConfig{Layouts: []string{time.RFC3339}}, Sources: EnvVars("tflag")}, toParse: []string{"--flag", "2006-11-02T15:04:05Z"}, - expect: `--flag Time (default: 2005-01-02 15:04:05 +0000 UTC)` + withEnvHint([]string{"tflag"}, ""), + expect: `--flag time (default: 2005-01-02 15:04:05 +0000 UTC)` + withEnvHint([]string{"tflag"}, ""), environ: map[string]string{ "tflag": "2010-01-02T15:04:05Z", }, @@ -2845,7 +2845,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) { name: "stringMap", flag: &StringMapFlag{Name: "flag", Value: map[string]string{"default1": "default2"}, Sources: EnvVars("ssflag")}, toParse: []string{"--flag", "parsed="}, - expect: `--flag value [ --flag value ] (default: default1="default2")` + withEnvHint([]string{"ssflag"}, ""), + expect: `--flag string=string [ --flag string=string ] (default: default1="default2")` + withEnvHint([]string{"ssflag"}, ""), environ: map[string]string{ "ssflag": "some-other-env_value=", }, @@ -3007,11 +3007,11 @@ var stringMapFlagTests = []struct { value map[string]string expected string }{ - {"foo", nil, nil, "--foo value [ --foo value ]\t"}, - {"f", nil, nil, "-f value [ -f value ]\t"}, - {"f", nil, map[string]string{"Lipstick": ""}, "-f value [ -f value ]\t(default: Lipstick=)"}, - {"test", nil, map[string]string{"Something": ""}, "--test value [ --test value ]\t(default: Something=)"}, - {"dee", []string{"d"}, map[string]string{"Inka": "Dinka", "dooo": ""}, "--dee value, -d value [ --dee value, -d value ]\t(default: Inka=\"Dinka\", dooo=)"}, + {"foo", nil, nil, "--foo string=string [ --foo string=string ]\t"}, + {"f", nil, nil, "-f string=string [ -f string=string ]\t"}, + {"f", nil, map[string]string{"Lipstick": ""}, "-f string=string [ -f string=string ]\t(default: Lipstick=)"}, + {"test", nil, map[string]string{"Something": ""}, "--test string=string [ --test string=string ]\t(default: Something=)"}, + {"dee", []string{"d"}, map[string]string{"Inka": "Dinka", "dooo": ""}, "--dee string=string, -d string=string [ --dee string=string, -d string=string ]\t(default: Inka=\"Dinka\", dooo=)"}, } func TestStringMapFlagHelpOutput(t *testing.T) { diff --git a/godoc-current.txt b/godoc-current.txt index d6ed0ddca6..d4743f5ba6 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -705,9 +705,6 @@ func (f *FlagBase[T, C, V]) GetDefaultText() string func (f *FlagBase[T, C, V]) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f *FlagBase[T, C, V]) GetFlagType() string - GetFlagType returns the type of the flag. - func (f *FlagBase[T, C, V]) GetUsage() string GetUsage returns the usage string for the flag @@ -748,6 +745,9 @@ func (f *FlagBase[T, C, V]) String() string func (f *FlagBase[T, C, V]) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +func (f *FlagBase[T, C, V]) TypeName() string + TypeName returns the type of the flag. + type FlagCategories interface { // AddFlags adds a flag to a category, creating a new category if necessary. AddFlag(category string, fl Flag) @@ -789,7 +789,7 @@ var FlagStringer FlagStringFunc = stringifyFlag display a flag. type FlagType interface { - GetFlagType() string + TypeName() string } FlagType is an interface to detect if a flag is a string, bool, etc. diff --git a/help_test.go b/help_test.go index 6ef9aaed31..30467da984 100644 --- a/help_test.go +++ b/help_test.go @@ -86,8 +86,8 @@ USAGE: test [global options] [arguments...] GLOBAL OPTIONS: - --foo int64, -f int64 - --help, -h show help + --foo int, -f int + --help, -h show help ` assert.Contains(t, output.String(), expected, @@ -1729,8 +1729,8 @@ GLOBAL OPTIONS: cat1 - --intd int64, --altd1 int64, --altd2 int64 (default: 0) - --m1 string + --intd int, --altd1 int, --altd2 int (default: 0) + --m1 string `, output.String()) } diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index d6ed0ddca6..d4743f5ba6 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -705,9 +705,6 @@ func (f *FlagBase[T, C, V]) GetDefaultText() string func (f *FlagBase[T, C, V]) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f *FlagBase[T, C, V]) GetFlagType() string - GetFlagType returns the type of the flag. - func (f *FlagBase[T, C, V]) GetUsage() string GetUsage returns the usage string for the flag @@ -748,6 +745,9 @@ func (f *FlagBase[T, C, V]) String() string func (f *FlagBase[T, C, V]) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +func (f *FlagBase[T, C, V]) TypeName() string + TypeName returns the type of the flag. + type FlagCategories interface { // AddFlags adds a flag to a category, creating a new category if necessary. AddFlag(category string, fl Flag) @@ -789,7 +789,7 @@ var FlagStringer FlagStringFunc = stringifyFlag display a flag. type FlagType interface { - GetFlagType() string + TypeName() string } FlagType is an interface to detect if a flag is a string, bool, etc.