Skip to content

Commit ef45965

Browse files
authored
Merge pull request #2005 from dearchap/add_generic_flag
Re-add generic flag back
2 parents 18c557e + b9f05fb commit ef45965

File tree

6 files changed

+247
-8
lines changed

6 files changed

+247
-8
lines changed

flag.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,6 @@ type LocalFlag interface {
180180
IsLocal() bool
181181
}
182182

183-
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
184-
func (f *FlagBase[T, C, V]) IsDefaultVisible() bool {
185-
return !f.HideDefault
186-
}
187-
188183
func newFlagSet(name string, flags []Flag) (*flag.FlagSet, error) {
189184
set := flag.NewFlagSet(name, flag.ContinueOnError)
190185

@@ -307,7 +302,6 @@ func stringifyFlag(f Flag) string {
307302
if !ok {
308303
return ""
309304
}
310-
311305
placeholder, usage := unquoteUsage(df.GetUsage())
312306
needsPlaceholder := df.TakesValue()
313307

flag_generic.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cli
2+
3+
type GenericFlag = FlagBase[Value, NoConfig, genericValue]
4+
5+
// -- Value Value
6+
type genericValue struct {
7+
val Value
8+
}
9+
10+
// Below functions are to satisfy the ValueCreator interface
11+
12+
func (f genericValue) Create(val Value, p *Value, c NoConfig) Value {
13+
*p = val
14+
return &genericValue{
15+
val: *p,
16+
}
17+
}
18+
19+
func (f genericValue) ToString(b Value) string {
20+
if b != nil {
21+
return b.String()
22+
}
23+
return ""
24+
}
25+
26+
// Below functions are to satisfy the flag.Value interface
27+
28+
func (f *genericValue) Set(s string) error {
29+
if f.val != nil {
30+
return f.val.Set(s)
31+
}
32+
return nil
33+
}
34+
35+
func (f *genericValue) Get() any {
36+
if f.val != nil {
37+
return f.val.Get()
38+
}
39+
return nil
40+
}
41+
42+
func (f *genericValue) String() string {
43+
if f.val != nil {
44+
return f.val.String()
45+
}
46+
return ""
47+
}
48+
49+
func (f *genericValue) IsBoolFlag() bool {
50+
if f.val == nil {
51+
return false
52+
}
53+
bf, ok := f.val.(boolFlag)
54+
return ok && bf.IsBoolFlag()
55+
}
56+
57+
// Generic looks up the value of a local GenericFlag, returns
58+
// nil if not found
59+
func (cmd *Command) Generic(name string) Value {
60+
if v, ok := cmd.Value(name).(Value); ok {
61+
tracef("generic available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
62+
return v
63+
}
64+
65+
tracef("generic NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
66+
return nil
67+
}

flag_impl.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
174174
return nil
175175
}
176176

177+
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
178+
func (f *FlagBase[T, C, V]) IsDefaultVisible() bool {
179+
return !f.HideDefault
180+
}
181+
177182
// String returns a readable representation of this value (for usage defaults)
178183
func (f *FlagBase[T, C, V]) String() string {
179184
return FlagStringer(f)
@@ -221,7 +226,7 @@ func (f *FlagBase[T, C, V]) GetEnvVars() []string {
221226
// TakesValue returns true if the flag takes a value, otherwise false
222227
func (f *FlagBase[T, C, V]) TakesValue() bool {
223228
var t T
224-
return reflect.TypeOf(t).Kind() != reflect.Bool
229+
return reflect.TypeOf(t) == nil || reflect.TypeOf(t).Kind() != reflect.Bool
225230
}
226231

227232
// GetDefaultText returns the default text for this flag
@@ -246,6 +251,9 @@ func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
246251
// values from cmd line. This is true for slice and map type flags
247252
func (f *FlagBase[T, C, VC]) IsMultiValueFlag() bool {
248253
// TBD how to specify
254+
if reflect.TypeOf(f.Value) == nil {
255+
return false
256+
}
249257
kind := reflect.TypeOf(f.Value).Kind()
250258
return kind == reflect.Slice || kind == reflect.Map
251259
}

flag_test.go

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,12 @@ func TestFlagsFromEnv(t *testing.T) {
238238
errContains: `could not parse "foobar" as []float64 value from environment ` +
239239
`variable "SECONDS" for flag seconds:`,
240240
},
241-
241+
{
242+
name: "Generic",
243+
input: "foo,bar",
244+
output: &Parser{"foo", "bar"},
245+
fl: &GenericFlag{Name: "names", Value: &Parser{}, Sources: EnvVars("NAMES")},
246+
},
242247
{
243248
name: "IntSliceFlag valid",
244249
input: "1,2",
@@ -461,6 +466,16 @@ func TestFlagStringifying(t *testing.T) {
461466
fl: &FloatSliceFlag{Name: "pepperonis", DefaultText: "shaved"},
462467
expected: "--pepperonis value [ --pepperonis value ]\t(default: shaved)",
463468
},
469+
{
470+
name: "generic-flag",
471+
fl: &GenericFlag{Name: "yogurt"},
472+
expected: "--yogurt value\t",
473+
},
474+
{
475+
name: "generic-flag-with-default-text",
476+
fl: &GenericFlag{Name: "ricotta", DefaultText: "plops"},
477+
expected: "--ricotta value\t(default: plops)",
478+
},
464479
{
465480
name: "int-flag",
466481
fl: &IntFlag{Name: "grubs"},
@@ -1558,6 +1573,85 @@ func TestFloat64SliceFlagApply_ParentCommand(t *testing.T) {
15581573
}).Run(buildTestContext(t), []string{"run", "child"})
15591574
}
15601575

1576+
var genericFlagTests = []struct {
1577+
name string
1578+
value Value
1579+
expected string
1580+
}{
1581+
{"toads", &Parser{"abc", "def"}, "--toads value\ttest flag (default: abc,def)"},
1582+
{"t", &Parser{"abc", "def"}, "-t value\ttest flag (default: abc,def)"},
1583+
}
1584+
1585+
func TestGenericFlagHelpOutput(t *testing.T) {
1586+
for _, test := range genericFlagTests {
1587+
fl := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
1588+
// create a temporary flag set to apply
1589+
tfs := flag.NewFlagSet("test", 0)
1590+
assert.NoError(t, fl.Apply(tfs))
1591+
assert.Equal(t, test.expected, fl.String())
1592+
}
1593+
}
1594+
1595+
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
1596+
defer resetEnv(os.Environ())
1597+
os.Clearenv()
1598+
_ = os.Setenv("APP_ZAP", "3")
1599+
1600+
for _, test := range genericFlagTests {
1601+
fl := &GenericFlag{Name: test.name, Sources: EnvVars("APP_ZAP")}
1602+
output := fl.String()
1603+
1604+
expectedSuffix := withEnvHint([]string{"APP_ZAP"}, "")
1605+
if !strings.HasSuffix(output, expectedSuffix) {
1606+
t.Errorf("%s does not end with"+expectedSuffix, output)
1607+
}
1608+
}
1609+
}
1610+
1611+
func TestGenericFlagApply_SetsAllNames(t *testing.T) {
1612+
fl := GenericFlag{Name: "orbs", Aliases: []string{"O", "obrs"}, Value: &Parser{}}
1613+
set := flag.NewFlagSet("test", 0)
1614+
assert.NoError(t, fl.Apply(set))
1615+
assert.NoError(t, set.Parse([]string{"--orbs", "eleventy,3", "-O", "4,bloop", "--obrs", "19,s"}))
1616+
}
1617+
1618+
func TestGenericFlagValueFromCommand(t *testing.T) {
1619+
cmd := &Command{
1620+
Name: "foo",
1621+
Flags: []Flag{
1622+
&GenericFlag{Name: "myflag", Value: &Parser{}},
1623+
},
1624+
}
1625+
1626+
assert.NoError(t, cmd.Run(buildTestContext(t), []string{"foo", "--myflag", "abc,def"}))
1627+
assert.Equal(t, &Parser{"abc", "def"}, cmd.Generic("myflag"))
1628+
assert.Nil(t, cmd.Generic("someother"))
1629+
}
1630+
1631+
func TestParseGenericFromEnv(t *testing.T) {
1632+
t.Setenv("APP_SERVE", "20,30")
1633+
cmd := &Command{
1634+
Flags: []Flag{
1635+
&GenericFlag{
1636+
Name: "serve",
1637+
Aliases: []string{"s"},
1638+
Value: &Parser{},
1639+
Sources: EnvVars("APP_SERVE"),
1640+
},
1641+
},
1642+
Action: func(ctx context.Context, cmd *Command) error {
1643+
if !reflect.DeepEqual(cmd.Generic("serve"), &Parser{"20", "30"}) {
1644+
t.Errorf("main name not set from env")
1645+
}
1646+
if !reflect.DeepEqual(cmd.Generic("s"), &Parser{"20", "30"}) {
1647+
t.Errorf("short name not set from env")
1648+
}
1649+
return nil
1650+
},
1651+
}
1652+
assert.NoError(t, cmd.Run(buildTestContext(t), []string{"run"}))
1653+
}
1654+
15611655
func TestParseMultiString(t *testing.T) {
15621656
_ = (&Command{
15631657
Flags: []Flag{
@@ -2756,6 +2850,16 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
27562850
"ssflag": "some-other-env_value=",
27572851
},
27582852
},
2853+
// TODO
2854+
/*{
2855+
name: "generic",
2856+
flag: &GenericFlag{Name: "flag", Value: &Parser{"11", "12"}, Sources: EnvVars("gflag")},
2857+
toParse: []string{"--flag", "15,16"},
2858+
expect: `--flag value (default: 11,12)` + withEnvHint([]string{"gflag"}, ""),
2859+
environ: map[string]string{
2860+
"gflag": "13,14",
2861+
},
2862+
},*/
27592863
}
27602864
for _, v := range cases {
27612865
for key, val := range v.environ {
@@ -3133,3 +3237,59 @@ func TestDocGetValue(t *testing.T) {
31333237
assert.Equal(t, "", (&BoolFlag{Name: "foo", Value: false}).GetValue())
31343238
assert.Equal(t, "bar", (&StringFlag{Name: "foo", Value: "bar"}).GetValue())
31353239
}
3240+
3241+
func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) {
3242+
var f Flag = &GenericFlag{}
3243+
3244+
_ = f.IsSet()
3245+
_ = f.Names()
3246+
}
3247+
3248+
func TestGenericValue_SatisfiesBoolInterface(t *testing.T) {
3249+
var f boolFlag = &genericValue{}
3250+
3251+
assert.False(t, f.IsBoolFlag())
3252+
3253+
fv := floatValue(0)
3254+
f = &genericValue{
3255+
val: &fv,
3256+
}
3257+
3258+
assert.False(t, f.IsBoolFlag())
3259+
3260+
f = &genericValue{
3261+
val: &boolValue{},
3262+
}
3263+
assert.True(t, f.IsBoolFlag())
3264+
}
3265+
3266+
func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) {
3267+
var f fmt.Stringer = &GenericFlag{}
3268+
3269+
_ = f.String()
3270+
}
3271+
3272+
func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
3273+
var f RequiredFlag = &GenericFlag{}
3274+
3275+
_ = f.IsRequired()
3276+
}
3277+
3278+
func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
3279+
var f VisibleFlag = &GenericFlag{}
3280+
3281+
_ = f.IsVisible()
3282+
}
3283+
3284+
func TestGenericFlag_SatisfiesDocFlagInterface(t *testing.T) {
3285+
var f DocGenerationFlag = &GenericFlag{}
3286+
3287+
_ = f.GetUsage()
3288+
}
3289+
3290+
func TestGenericValue(t *testing.T) {
3291+
g := &genericValue{}
3292+
assert.NoError(t, g.Set("something"))
3293+
assert.Nil(t, g.Get())
3294+
assert.Empty(t, g.String())
3295+
}

godoc-current.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ func (cmd *Command) FullName() string
443443
FullName returns the full name of the command. For commands with parents
444444
this ensures that the parent commands are part of the command path.
445445

446+
func (cmd *Command) Generic(name string) Value
447+
Generic looks up the value of a local GenericFlag, returns nil if not found
448+
446449
func (cmd *Command) HasName(name string) bool
447450
HasName returns true if Command.Name matches given name
448451

@@ -793,6 +796,8 @@ type FloatSlice = SliceBase[float64, NoConfig, floatValue]
793796

794797
type FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
795798

799+
type GenericFlag = FlagBase[Value, NoConfig, genericValue]
800+
796801
type IntArg = ArgumentBase[int64, IntegerConfig, intValue]
797802

798803
type IntFlag = FlagBase[int64, IntegerConfig, intValue]

testdata/godoc-v3.x.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ func (cmd *Command) FullName() string
443443
FullName returns the full name of the command. For commands with parents
444444
this ensures that the parent commands are part of the command path.
445445

446+
func (cmd *Command) Generic(name string) Value
447+
Generic looks up the value of a local GenericFlag, returns nil if not found
448+
446449
func (cmd *Command) HasName(name string) bool
447450
HasName returns true if Command.Name matches given name
448451

@@ -793,6 +796,8 @@ type FloatSlice = SliceBase[float64, NoConfig, floatValue]
793796

794797
type FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
795798

799+
type GenericFlag = FlagBase[Value, NoConfig, genericValue]
800+
796801
type IntArg = ArgumentBase[int64, IntegerConfig, intValue]
797802

798803
type IntFlag = FlagBase[int64, IntegerConfig, intValue]

0 commit comments

Comments
 (0)