Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-add generic flag back #2005

Merged
merged 4 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,6 @@ type LocalFlag interface {
IsLocal() bool
}

// IsDefaultVisible returns true if the flag is not hidden, otherwise false
func (f *FlagBase[T, C, V]) IsDefaultVisible() bool {
return !f.HideDefault
}

func newFlagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)

Expand Down Expand Up @@ -307,7 +302,6 @@ func stringifyFlag(f Flag) string {
if !ok {
return ""
}

placeholder, usage := unquoteUsage(df.GetUsage())
needsPlaceholder := df.TakesValue()

Expand Down
67 changes: 67 additions & 0 deletions flag_generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package cli

type GenericFlag = FlagBase[Value, NoConfig, genericValue]
dearchap marked this conversation as resolved.
Show resolved Hide resolved

// -- Value Value
type genericValue struct {
val Value
}

// Below functions are to satisfy the ValueCreator interface

func (f genericValue) Create(val Value, p *Value, c NoConfig) Value {
*p = val
return &genericValue{
val: *p,
}
}

func (f genericValue) ToString(b Value) string {
if b != nil {
return b.String()
}
return ""
}

// Below functions are to satisfy the flag.Value interface

func (f *genericValue) Set(s string) error {
if f.val != nil {
return f.val.Set(s)
}
return nil
}

func (f *genericValue) Get() any {
if f.val != nil {
return f.val.Get()
}
return nil
}

func (f *genericValue) String() string {
if f.val != nil {
return f.val.String()
}
return ""
}

func (f *genericValue) IsBoolFlag() bool {
if f.val == nil {
return false
}
bf, ok := f.val.(boolFlag)
return ok && bf.IsBoolFlag()
}

// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (cmd *Command) Generic(name string) Value {
if v, ok := cmd.Value(name).(Value); ok {
tracef("generic available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}

tracef("generic NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
10 changes: 9 additions & 1 deletion flag_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
return nil
}

// IsDefaultVisible returns true if the flag is not hidden, otherwise false
func (f *FlagBase[T, C, V]) IsDefaultVisible() bool {
return !f.HideDefault
}

// String returns a readable representation of this value (for usage defaults)
func (f *FlagBase[T, C, V]) String() string {
return FlagStringer(f)
Expand Down Expand Up @@ -221,7 +226,7 @@ func (f *FlagBase[T, C, V]) GetEnvVars() []string {
// TakesValue returns true if the flag takes a value, otherwise false
func (f *FlagBase[T, C, V]) TakesValue() bool {
var t T
return reflect.TypeOf(t).Kind() != reflect.Bool
return reflect.TypeOf(t) == nil || reflect.TypeOf(t).Kind() != reflect.Bool
}

// GetDefaultText returns the default text for this flag
Expand All @@ -246,6 +251,9 @@ func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
// values from cmd line. This is true for slice and map type flags
func (f *FlagBase[T, C, VC]) IsMultiValueFlag() bool {
// TBD how to specify
if reflect.TypeOf(f.Value) == nil {
return false
}
kind := reflect.TypeOf(f.Value).Kind()
return kind == reflect.Slice || kind == reflect.Map
}
Expand Down
162 changes: 161 additions & 1 deletion flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,12 @@ func TestFlagsFromEnv(t *testing.T) {
errContains: `could not parse "foobar" as []float64 value from environment ` +
`variable "SECONDS" for flag seconds:`,
},

{
name: "Generic",
input: "foo,bar",
output: &Parser{"foo", "bar"},
fl: &GenericFlag{Name: "names", Value: &Parser{}, Sources: EnvVars("NAMES")},
},
{
name: "IntSliceFlag valid",
input: "1,2",
Expand Down Expand Up @@ -461,6 +466,16 @@ func TestFlagStringifying(t *testing.T) {
fl: &FloatSliceFlag{Name: "pepperonis", DefaultText: "shaved"},
expected: "--pepperonis value [ --pepperonis value ]\t(default: shaved)",
},
{
name: "generic-flag",
fl: &GenericFlag{Name: "yogurt"},
expected: "--yogurt value\t",
},
{
name: "generic-flag-with-default-text",
fl: &GenericFlag{Name: "ricotta", DefaultText: "plops"},
expected: "--ricotta value\t(default: plops)",
},
{
name: "int-flag",
fl: &IntFlag{Name: "grubs"},
Expand Down Expand Up @@ -1558,6 +1573,85 @@ func TestFloat64SliceFlagApply_ParentCommand(t *testing.T) {
}).Run(buildTestContext(t), []string{"run", "child"})
}

var genericFlagTests = []struct {
name string
value Value
expected string
}{
{"toads", &Parser{"abc", "def"}, "--toads value\ttest flag (default: abc,def)"},
{"t", &Parser{"abc", "def"}, "-t value\ttest flag (default: abc,def)"},
}

func TestGenericFlagHelpOutput(t *testing.T) {
for _, test := range genericFlagTests {
fl := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
// create a temporary flag set to apply
tfs := flag.NewFlagSet("test", 0)
assert.NoError(t, fl.Apply(tfs))
assert.Equal(t, test.expected, fl.String())
}
}

func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("APP_ZAP", "3")

for _, test := range genericFlagTests {
fl := &GenericFlag{Name: test.name, Sources: EnvVars("APP_ZAP")}
output := fl.String()

expectedSuffix := withEnvHint([]string{"APP_ZAP"}, "")
if !strings.HasSuffix(output, expectedSuffix) {
t.Errorf("%s does not end with"+expectedSuffix, output)
}
}
}

func TestGenericFlagApply_SetsAllNames(t *testing.T) {
fl := GenericFlag{Name: "orbs", Aliases: []string{"O", "obrs"}, Value: &Parser{}}
set := flag.NewFlagSet("test", 0)
assert.NoError(t, fl.Apply(set))
assert.NoError(t, set.Parse([]string{"--orbs", "eleventy,3", "-O", "4,bloop", "--obrs", "19,s"}))
}

func TestGenericFlagValueFromCommand(t *testing.T) {
cmd := &Command{
Name: "foo",
Flags: []Flag{
&GenericFlag{Name: "myflag", Value: &Parser{}},
},
}

assert.NoError(t, cmd.Run(buildTestContext(t), []string{"foo", "--myflag", "abc,def"}))
assert.Equal(t, &Parser{"abc", "def"}, cmd.Generic("myflag"))
assert.Nil(t, cmd.Generic("someother"))
}

func TestParseGenericFromEnv(t *testing.T) {
t.Setenv("APP_SERVE", "20,30")
cmd := &Command{
Flags: []Flag{
&GenericFlag{
Name: "serve",
Aliases: []string{"s"},
Value: &Parser{},
Sources: EnvVars("APP_SERVE"),
},
},
Action: func(ctx context.Context, cmd *Command) error {
if !reflect.DeepEqual(cmd.Generic("serve"), &Parser{"20", "30"}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(cmd.Generic("s"), &Parser{"20", "30"}) {
t.Errorf("short name not set from env")
}
return nil
},
}
assert.NoError(t, cmd.Run(buildTestContext(t), []string{"run"}))
}

func TestParseMultiString(t *testing.T) {
_ = (&Command{
Flags: []Flag{
Expand Down Expand Up @@ -2756,6 +2850,16 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
"ssflag": "some-other-env_value=",
},
},
// TODO
/*{
name: "generic",
flag: &GenericFlag{Name: "flag", Value: &Parser{"11", "12"}, Sources: EnvVars("gflag")},
toParse: []string{"--flag", "15,16"},
expect: `--flag value (default: 11,12)` + withEnvHint([]string{"gflag"}, ""),
environ: map[string]string{
"gflag": "13,14",
},
},*/
}
for _, v := range cases {
for key, val := range v.environ {
Expand Down Expand Up @@ -3133,3 +3237,59 @@ func TestDocGetValue(t *testing.T) {
assert.Equal(t, "", (&BoolFlag{Name: "foo", Value: false}).GetValue())
assert.Equal(t, "bar", (&StringFlag{Name: "foo", Value: "bar"}).GetValue())
}

func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) {
var f Flag = &GenericFlag{}

_ = f.IsSet()
_ = f.Names()
}

func TestGenericValue_SatisfiesBoolInterface(t *testing.T) {
var f boolFlag = &genericValue{}

assert.False(t, f.IsBoolFlag())

fv := floatValue(0)
f = &genericValue{
val: &fv,
}

assert.False(t, f.IsBoolFlag())

f = &genericValue{
val: &boolValue{},
}
assert.True(t, f.IsBoolFlag())
}

func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &GenericFlag{}

_ = f.String()
}

func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f RequiredFlag = &GenericFlag{}

_ = f.IsRequired()
}

func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f VisibleFlag = &GenericFlag{}

_ = f.IsVisible()
}

func TestGenericFlag_SatisfiesDocFlagInterface(t *testing.T) {
var f DocGenerationFlag = &GenericFlag{}

_ = f.GetUsage()
}

func TestGenericValue(t *testing.T) {
g := &genericValue{}
assert.NoError(t, g.Set("something"))
assert.Nil(t, g.Get())
assert.Empty(t, g.String())
}
5 changes: 5 additions & 0 deletions godoc-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,9 @@ func (cmd *Command) FullName() string
FullName returns the full name of the command. For commands with parents
this ensures that the parent commands are part of the command path.

func (cmd *Command) Generic(name string) Value
Generic looks up the value of a local GenericFlag, returns nil if not found

func (cmd *Command) HasName(name string) bool
HasName returns true if Command.Name matches given name

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

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

type GenericFlag = FlagBase[Value, NoConfig, genericValue]

type IntArg = ArgumentBase[int64, IntegerConfig, intValue]

type IntFlag = FlagBase[int64, IntegerConfig, intValue]
Expand Down
5 changes: 5 additions & 0 deletions testdata/godoc-v3.x.txt
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,9 @@ func (cmd *Command) FullName() string
FullName returns the full name of the command. For commands with parents
this ensures that the parent commands are part of the command path.

func (cmd *Command) Generic(name string) Value
Generic looks up the value of a local GenericFlag, returns nil if not found

func (cmd *Command) HasName(name string) bool
HasName returns true if Command.Name matches given name

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

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

type GenericFlag = FlagBase[Value, NoConfig, genericValue]

type IntArg = ArgumentBase[int64, IntegerConfig, intValue]

type IntFlag = FlagBase[int64, IntegerConfig, intValue]
Expand Down
Loading