diff --git a/command_parse.go b/command_parse.go index aa95ae1677..63d859c42c 100644 --- a/command_parse.go +++ b/command_parse.go @@ -80,6 +80,7 @@ func (cmd *Command) parseFlags(args Args) (Args, error) { firstArg := strings.TrimSpace(rargs[0]) if len(firstArg) == 0 { + posArgs = append(posArgs, rargs[0:]...) break } diff --git a/command_setup.go b/command_setup.go index cac4a30314..7c7776c2d2 100644 --- a/command_setup.go +++ b/command_setup.go @@ -66,7 +66,7 @@ func (cmd *Command) setupDefaults(osArgs []string) { flag.VisitAll(func(f *flag.Flag) { // skip test flags if !strings.HasPrefix(f.Name, ignoreFlagPrefix) { - cmd.Flags = append(cmd.Flags, &extFlag{f}) + cmd.Flags = append(cmd.Flags, &extFlag{f, ""}) } }) } diff --git a/command_test.go b/command_test.go index 424e8c0eda..ede1bda84f 100644 --- a/command_test.go +++ b/command_test.go @@ -867,12 +867,12 @@ var defaultCommandSubCommandTests = []struct { {"", "jimbob", "foobar", true}, {"", "j", "foobar", true}, {"", "carly", "foobar", true}, - {"", "jimmers", "foobar", true}, + {"", "jimmers", "foobar", false}, {"", "jimmers", "", true}, - {" ", "jimmers", "foobar", true}, - /*{"", "", "", true}, + {" ", "jimmers", "foobar", false}, + {"", "", "", true}, {" ", "", "", false}, - {" ", "j", "", false},*/ + {" ", "j", "", false}, {"bat", "", "batbaz", true}, {"nothing", "", "batbaz", true}, {"nothing", "", "", false}, @@ -917,6 +917,7 @@ var defaultCommandFlagTests = []struct { }{ {"foobar", "", "foobar", true}, {"foobar", "-c derp", "foobar", true}, + {"foobar", "-c=", "foobar", true}, {"batbaz", "", "foobar", true}, {"b", "", "", true}, {"f", "", "", true}, @@ -930,13 +931,14 @@ var defaultCommandFlagTests = []struct { {"", "-j", "", true}, {" ", "-j", "foobar", true}, {"", "", "", true}, - {" ", "", "", true}, - {" ", "-j", "", true}, + {" ", "", "", false}, + {" ", "-j", "", false}, {"bat", "", "batbaz", true}, {"nothing", "", "batbaz", true}, {"nothing", "", "", false}, {"nothing", "--jimbob", "batbaz", true}, {"nothing", "--carly", "", false}, + {"nothing", "--carly=", "", false}, } func TestCommand_RunDefaultCommandWithFlags(t *testing.T) { diff --git a/flag_ext.go b/flag_ext.go index 9972af7c56..16e8966df8 100644 --- a/flag_ext.go +++ b/flag_ext.go @@ -1,14 +1,72 @@ package cli -import "flag" +import ( + "context" + "flag" + "fmt" + "reflect" + "strings" +) + +var _ Value = (*externalValue)(nil) + +// -- Value Value +type externalValue struct { + e *extFlag +} + +// Below functions are to satisfy the flag.Value interface + +func (ev *externalValue) Set(s string) error { + if ev != nil && ev.e.f != nil { + return ev.e.f.Value.Set(s) + } + return nil +} + +func (ev *externalValue) Get() any { + if ev != nil && ev.e.f != nil { + return ev.e.f.Value.(flag.Getter).Get() + } + return nil +} + +func (ev *externalValue) String() string { + if ev != nil && ev.e.f != nil { + return ev.e.String() + } + return "" +} + +func (ev *externalValue) IsBoolFlag() bool { + if ev == nil || ev.e.f == nil { + return false + } + bf, ok := ev.e.f.Value.(boolFlag) + return ok && bf.IsBoolFlag() +} + +var _ Flag = (*extFlag)(nil) +var _ ActionableFlag = (*extFlag)(nil) +var _ CategorizableFlag = (*extFlag)(nil) +var _ DocGenerationFlag = (*extFlag)(nil) +var _ DocGenerationMultiValueFlag = (*extFlag)(nil) +var _ LocalFlag = (*extFlag)(nil) +var _ RequiredFlag = (*extFlag)(nil) +var _ VisibleFlag = (*extFlag)(nil) type extFlag struct { - f *flag.Flag + f *flag.Flag + category string } func (e *extFlag) PreParse() error { if e.f.DefValue != "" { - return e.Set("", e.f.DefValue) + // suppress errors for write-only external flags that always return nil + if err := e.Set("", e.f.DefValue); err != nil && e.f.Value.(flag.Getter).Get() != nil { + // wrap error with some context for the user + return fmt.Errorf("external flag --%s default %q: %w", e.f.Name, e.f.DefValue, err) + } } return nil @@ -30,6 +88,43 @@ func (e *extFlag) Names() []string { return []string{e.f.Name} } +// IsBoolFlag returns whether the flag doesn't need to accept args +func (e *extFlag) IsBoolFlag() bool { + if e == nil || e.f == nil { + return false + } + return (&externalValue{e}).IsBoolFlag() +} + +// IsDefaultVisible returns true if the flag is not hidden, otherwise false +func (e *extFlag) IsDefaultVisible() bool { + return true +} + +// IsLocal returns false if flag needs to be persistent across subcommands +func (e *extFlag) IsLocal() bool { + return false +} + +// IsMultiValueFlag returns true if the value type T can take multiple +// values from cmd line. This is true for slice and map type flags +func (e *extFlag) IsMultiValueFlag() bool { + if e == nil || e.f == nil { + return false + } + // TBD how to specify + if reflect.TypeOf(e.f.Value) == nil { + return false + } + kind := reflect.TypeOf(e.f.Value).Kind() + return kind == reflect.Slice || kind == reflect.Map +} + +// IsRequired returns whether or not the flag is required +func (e *extFlag) IsRequired() bool { + return false +} + func (e *extFlag) IsSet() bool { return false } @@ -61,3 +156,61 @@ func (e *extFlag) GetDefaultText() string { func (e *extFlag) GetEnvVars() []string { return nil } + +// RunAction executes flag action if set +func (e *extFlag) RunAction(ctx context.Context, cmd *Command) error { + return nil +} + +// TypeName returns the type of the flag. +func (e *extFlag) TypeName() string { + if e == nil || e.f == nil { + return "" + } + ty := reflect.TypeOf(e.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? + case reflect.Slice: + elemType := ty.Elem() + 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()) + } +} + +// GetCategory returns the category of the flag +func (e *extFlag) GetCategory() string { + if e == nil { + return "" + } + return e.category +} + +func (e *extFlag) SetCategory(c string) { + if e != nil { + e.category = c + } +}