diff --git a/app_test.go b/app_test.go index 600f2cb..9a1fcd1 100644 --- a/app_test.go +++ b/app_test.go @@ -956,6 +956,37 @@ func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) { expect(t, err, errors.New("flag needs an argument: -n")) } +func TestApp_UseShortOptionAfterSliceFlag(t *testing.T) { + var one, two bool + var name string + var sliceValDest StringSlice + var sliceVal []string + expected := "expectedName" + + app := newTestApp() + app.UseShortOptionHandling = true + app.Flags = []Flag{ + &StringSliceFlag{Name: "env", Aliases: []string{"e"}, Destination: &sliceValDest}, + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, + } + app.Action = func(c *Context) error { + sliceVal = c.StringSlice("env") + one = c.Bool("one") + two = c.Bool("two") + name = c.String("name") + return nil + } + + _ = app.Run([]string{"", "-e", "foo", "-on", expected}) + expect(t, sliceVal, []string{"foo"}) + expect(t, sliceValDest.Value(), []string{"foo"}) + expect(t, one, true) + expect(t, two, false) + expect(t, name, expected) +} + func TestApp_Float64Flag(t *testing.T) { var meters float64 diff --git a/flag.go b/flag.go index 6aff19b..b719f8b 100644 --- a/flag.go +++ b/flag.go @@ -126,6 +126,21 @@ type Flag interface { RunAction(*Context) error } +// DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + // DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. type DocGenerationSliceFlag interface { DocGenerationFlag @@ -293,8 +308,14 @@ func formatDefault(format string) string { } func stringifyFlag(f Flag) string { - placeholder, usage := unquoteUsage(f.GetUsage()) - needsPlaceholder := f.TakesValue() + // enforce DocGeneration interface on flags to avoid reflection + df, ok := f.(DocGenerationFlag) + if !ok { + return "" + } + + placeholder, usage := unquoteUsage(df.GetUsage()) + needsPlaceholder := df.TakesValue() if needsPlaceholder && placeholder == "" { placeholder = defaultPlaceholder @@ -302,7 +323,7 @@ func stringifyFlag(f Flag) string { defaultValueString := "" - if s := f.GetDefaultText(); s != "" { + if s := df.GetDefaultText(); s != "" { defaultValueString = fmt.Sprintf(formatDefault("%s"), s) } diff --git a/parse.go b/parse.go index a2db306..28e24f1 100644 --- a/parse.go +++ b/parse.go @@ -46,7 +46,10 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple } // swap current argument with the split version - args = append(args[:i], append(shortOpts, args[i+1:]...)...) + // do not include args that parsed correctly so far as it would + // trigger Value.Set() on those args and would result in + // duplicates for slice type flags + args = append(shortOpts, args[i+1:]...) argsWereSplit = true break }