From 1fb1288bf36d5955301ee74355959e8cd361f385 Mon Sep 17 00:00:00 2001 From: Ian Campbell Date: Fri, 25 Jan 2019 15:16:59 +0000 Subject: [PATCH] Add a method to expose unknown flags back to the user Currently it is possible to ignore unknown flags (with `FlagSet.ParseErrorsWhitelist.UnknownFlags`) but there is no way to find out what they were after the fact. Add a method which registers a slice into which all unknown arguments will be accumulated. Note that unknown short arguments which appear in a group (e.g. `-uuu`) will be exploded (e.g. into `-u -u -u`) in this slice. Also any following value is associated with the final option, e.g. `["-uuu", "foo"]` is interpreted as `["-u", "-u", "-u", "foo"]`. This is consistent with the statement: Single dashes signify a series of shorthand letters for flags. All but the last shorthand letter must be boolean flags. This is the reason why the call to `stripUnknownFlagValue` in `parseSingleShortArg` becomes conditional on this being the last short arg in the batch. Add code to the existing `TestIgnoreUnknownFlags` (which already has comprehensive set of the varities of possible unknown flags) to check this. Also fixed a small cut-and-paste-o in the failure message of the test (use of `f.ParseAll`). Signed-off-by: Ian Campbell --- flag.go | 39 +++++++++++++++++++++++++++++++++++---- flag_test.go | 22 +++++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/flag.go b/flag.go index 9beeda8e..373fc61f 100644 --- a/flag.go +++ b/flag.go @@ -126,7 +126,10 @@ const ( // ParseErrorsWhitelist defines the parsing errors that can be ignored type ParseErrorsWhitelist struct { - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags + // UnknownFlags will ignore unknown flags errors and continue + // parsing rest of the flags. Consider using + // SetUnknownFlagsSlice if you need to know which unknown + // flags occured UnknownFlags bool } @@ -163,6 +166,7 @@ type FlagSet struct { output io.Writer // nil means stderr; use out() accessor interspersed bool // allow interspersed option/non-option args normalizeNameFunc func(f *FlagSet, name string) NormalizedName + unknownflags *[]string addedGoFlagSets []*goflag.FlagSet } @@ -915,10 +919,17 @@ func (f *FlagSet) usage() { } } +func (f *FlagSet) addUnknownFlag(s string) { + if f.unknownflags == nil { + return + } + *f.unknownflags = append(*f.unknownflags, s) +} + //--unknown (args will be empty) //--unknown --next-flag ... (args will be --next-flag ...) //--unknown arg ... (args will be arg ...) -func stripUnknownFlagValue(args []string) []string { +func (f *FlagSet) stripUnknownFlagValue(args []string) []string { if len(args) == 0 { //--unknown return args @@ -932,6 +943,7 @@ func stripUnknownFlagValue(args []string) []string { //--unknown arg ... (args will be arg ...) if len(args) > 1 { + f.addUnknownFlag(args[0]) return args[1:] } return nil @@ -955,13 +967,14 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin f.usage() return a, ErrHelp case f.ParseErrorsWhitelist.UnknownFlags: + f.addUnknownFlag(s) // --unknown=unknownval arg ... // we do not want to lose arg in this case if len(split) >= 2 { return a, nil } - return stripUnknownFlagValue(a), nil + return f.stripUnknownFlagValue(a), nil default: err = f.failf("unknown flag: --%s", name) return @@ -1013,11 +1026,15 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse // '-f=arg arg ...' // we do not want to lose arg in this case if len(shorthands) > 2 && shorthands[1] == '=' { + f.addUnknownFlag("-" + shorthands) outShorts = "" return } - outArgs = stripUnknownFlagValue(outArgs) + f.addUnknownFlag("-" + string(c)) + if len(outShorts) == 0 { + outArgs = f.stripUnknownFlagValue(outArgs) + } return default: err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) @@ -1171,6 +1188,13 @@ func (f *FlagSet) Parsed() bool { return f.parsed } +// SetUnknownFlagsSlice arranges to append any unknown flags to the +// given slice. This requires ParseErrorsWhitelist.UnknownFlags to be +// set so that parsing does not abort on the first unknown flag. +func (f *FlagSet) SetUnknownFlagsSlice(s *[]string) { + f.unknownflags = s +} + // Parse parses the command-line flags from os.Args[1:]. Must be called // after all flags are defined and before flags are accessed by the program. func Parse() { @@ -1196,6 +1220,13 @@ func Parsed() bool { return CommandLine.Parsed() } +// SetUnknownFlagsSlice arranges to append any unknown flags to the +// given slice. This requires ParseErrorsWhitelist.UnknownFlags to be +// set so that parsing does not abort on the first unknown flag. +func SetUnknownFlagsSlice(s *[]string) { + CommandLine.SetUnknownFlagsSlice(s) +} + // CommandLine is the default set of command-line flags, parsed from os.Args. var CommandLine = NewFlagSet(os.Args[0], ExitOnError) diff --git a/flag_test.go b/flag_test.go index 7d02dbc8..b2820a07 100644 --- a/flag_test.go +++ b/flag_test.go @@ -399,6 +399,8 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { t.Error("f.Parse() = true before Parse") } f.ParseErrorsWhitelist.UnknownFlags = true + var unknownFlags []string + f.SetUnknownFlagsSlice(&unknownFlags) f.BoolP("boola", "a", false, "bool value") f.BoolP("boolb", "b", false, "bool2 value") @@ -449,6 +451,19 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { "stringo", "ovalue", "boole", "true", } + wantUnknowns := []string{ + "--unknown1", "unknown1Value", + "--unknown2=unknown2Value", + "-u=unknown3Value", + "-p", "unknown4Value", + "-q", + "--unknown7=unknown7value", + "--unknown8=unknown8value", + "--unknown6", "", + "-u", "-u", "-u", "-u", "-u", "", + "--unknown10", + "--unknown11", + } got := []string{} store := func(flag *Flag, value string) error { got = append(got, flag.Name) @@ -464,10 +479,15 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { t.Errorf("f.Parse() = false after Parse") } if !reflect.DeepEqual(got, want) { - t.Errorf("f.ParseAll() fail to restore the args") + t.Errorf("f.Parse() failed to parse with unknown args") t.Errorf("Got: %v", got) t.Errorf("Want: %v", want) } + if !reflect.DeepEqual(unknownFlags, wantUnknowns) { + t.Errorf("f.Parse() failed to enumerate the unknown args args") + t.Errorf("Got: %v", unknownFlags) + t.Errorf("Want: %v", wantUnknowns) + } } func TestShorthand(t *testing.T) {