From bbf05e31fbc75782f9a2b968dba71ccce1f4de54 Mon Sep 17 00:00:00 2001 From: Rajat Jindal Date: Sun, 11 Mar 2018 22:47:11 -0700 Subject: [PATCH 1/5] add ability to ignore unknown flags --- flag.go | 46 ++++++++++++++++++++++++++++++++----- flag_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/flag.go b/flag.go index 28538c07..25478555 100644 --- a/flag.go +++ b/flag.go @@ -138,6 +138,9 @@ type FlagSet struct { // help/usage messages. SortFlags bool + // IgnoreUnknownFlags is used to indicate to ignore unknown flags + IgnoreUnknownFlags bool + name string parsed bool actual map[NormalizedName]*Flag @@ -896,6 +899,25 @@ func (f *FlagSet) usage() { } } +//--unknown (args will be empty) +//--unknown --next-flag ... (args will be --next-flag ...) +//--unknown arg ... (args will be arg ...) +func stripUnknownFlagValue(args []string) []string { + if len(args) == 0 { + //--unknown + return args + } + + first := args[0] + if first[0] == '-' { + //--unknown --next-flag ... + return args + } + + //--unknown arg ... (args will be arg ...) + return args[1:] +} + func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) { a = args name := s[2:] @@ -907,13 +929,18 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin split := strings.SplitN(name, "=", 2) name = split[0] flag, exists := f.formal[f.normalizeFlagName(name)] + if !exists { - if name == "help" { // special case for nice help message. + switch { + case name == "help": f.usage() return a, ErrHelp + case f.IgnoreUnknownFlags: + return stripUnknownFlagValue(a), nil + default: + err = f.failf("unknown flag: --%s", name) + return } - err = f.failf("unknown flag: --%s", name) - return } var value string @@ -941,6 +968,8 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin } func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) { + fmt.Printf("inside short - flagset %s - %v", shorthands, args) + if strings.HasPrefix(shorthands, "test.") { return } @@ -951,13 +980,18 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse flag, exists := f.shorthands[c] if !exists { - if c == 'h' { // special case for nice help message. + switch { + case c == 'h': f.usage() err = ErrHelp return + case f.IgnoreUnknownFlags: + args = stripUnknownFlagValue(args) + return + default: + err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) + return } - err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) - return } var value string diff --git a/flag_test.go b/flag_test.go index d587752f..3241b2dd 100644 --- a/flag_test.go +++ b/flag_test.go @@ -394,6 +394,66 @@ func testParseAll(f *FlagSet, t *testing.T) { } } +func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + f.IgnoreUnknownFlags = true + + f.BoolP("boola", "a", false, "bool value") + f.BoolP("boolb", "b", false, "bool2 value") + f.BoolP("boolc", "c", false, "bool3 value") + f.BoolP("boold", "d", false, "bool4 value") + f.StringP("stringa", "s", "0", "string value") + f.StringP("stringz", "z", "0", "string value") + f.StringP("stringx", "x", "0", "string value") + f.StringP("stringy", "y", "0", "string value") + f.Lookup("stringx").NoOptDefVal = "1" + args := []string{ + "-ab", + "-cs=xx", + "--stringz=something", + "--unknown1", + "unknown1Value", + "-d=true", + "-x", + "--unknown2=unknown2Value", + "-u=unknown3Value", + "-y", + "ee", + "--unknown4", + } + want := []string{ + "boola", "true", + "boolb", "true", + "boolc", "true", + "stringa", "xx", + "stringz", "something", + "boold", "true", + "stringx", "1", + "stringy", "ee", + } + got := []string{} + store := func(flag *Flag, value string) error { + got = append(got, flag.Name) + if len(value) > 0 { + got = append(got, value) + } + return nil + } + if err := f.ParseAll(args, store); err != nil { + t.Errorf("expected no error, got %s", err) + } + if !f.Parsed() { + t.Errorf("f.Parse() = false after Parse") + } + if !reflect.DeepEqual(got, want) { + t.Errorf("f.ParseAll() fail to restore the args") + t.Errorf("Got: %v", got) + t.Errorf("Want: %v", want) + } +} + func TestShorthand(t *testing.T) { f := NewFlagSet("shorthand", ContinueOnError) if f.Parsed() { @@ -500,6 +560,11 @@ func TestParseAll(t *testing.T) { testParseAll(GetCommandLine(), t) } +func TestIgnoreUnknownFlags(t *testing.T) { + ResetForTesting(func() { t.Error("bad parse") }) + testParseAll(GetCommandLine(), t) +} + func TestFlagSetParse(t *testing.T) { testParse(NewFlagSet("test", ContinueOnError), t) } From 56e2774ee92f8e15bd81519552354923f3911942 Mon Sep 17 00:00:00 2001 From: Rajat Jindal Date: Sun, 11 Mar 2018 23:48:59 -0700 Subject: [PATCH 2/5] add testcases --- flag.go | 8 +++++--- flag_test.go | 11 +++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/flag.go b/flag.go index 25478555..c6954c66 100644 --- a/flag.go +++ b/flag.go @@ -968,8 +968,6 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin } func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) { - fmt.Printf("inside short - flagset %s - %v", shorthands, args) - if strings.HasPrefix(shorthands, "test.") { return } @@ -986,7 +984,11 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse err = ErrHelp return case f.IgnoreUnknownFlags: - args = stripUnknownFlagValue(args) + outArgs = stripUnknownFlagValue(outArgs) + if len(shorthands) > 2 && shorthands[1] == '=' { + // '-f=arg' + outShorts = "" + } return default: err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) diff --git a/flag_test.go b/flag_test.go index 3241b2dd..09cac58f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -389,7 +389,7 @@ func testParseAll(f *FlagSet, t *testing.T) { } if !reflect.DeepEqual(got, want) { t.Errorf("f.ParseAll() fail to restore the args") - t.Errorf("Got: %v", got) + t.Errorf("Got: %v", got) t.Errorf("Want: %v", want) } } @@ -419,9 +419,12 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { "-x", "--unknown2=unknown2Value", "-u=unknown3Value", + "-p", + "unknown4Value", + "-q", //another unknown with bool value "-y", "ee", - "--unknown4", + "--unknown6", } want := []string{ "boola", "true", @@ -449,7 +452,7 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { } if !reflect.DeepEqual(got, want) { t.Errorf("f.ParseAll() fail to restore the args") - t.Errorf("Got: %v", got) + t.Errorf("Got: %v", got) t.Errorf("Want: %v", want) } } @@ -562,7 +565,7 @@ func TestParseAll(t *testing.T) { func TestIgnoreUnknownFlags(t *testing.T) { ResetForTesting(func() { t.Error("bad parse") }) - testParseAll(GetCommandLine(), t) + testParseWithUnknownFlags(GetCommandLine(), t) } func TestFlagSetParse(t *testing.T) { From eabdfa9a7a9d375d45154d09dda336e6a52ef2e5 Mon Sep 17 00:00:00 2001 From: Rajat Jindal Date: Wed, 28 Mar 2018 07:03:31 -0700 Subject: [PATCH 3/5] add 2 more patterns to the testcase --- flag_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/flag_test.go b/flag_test.go index 09cac58f..e0047e69 100644 --- a/flag_test.go +++ b/flag_test.go @@ -404,10 +404,12 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { f.BoolP("boolb", "b", false, "bool2 value") f.BoolP("boolc", "c", false, "bool3 value") f.BoolP("boold", "d", false, "bool4 value") + f.BoolP("boole", "e", false, "bool4 value") f.StringP("stringa", "s", "0", "string value") f.StringP("stringz", "z", "0", "string value") f.StringP("stringx", "x", "0", "string value") f.StringP("stringy", "y", "0", "string value") + f.StringP("stringo", "o", "0", "string value") f.Lookup("stringx").NoOptDefVal = "1" args := []string{ "-ab", @@ -424,6 +426,10 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { "-q", //another unknown with bool value "-y", "ee", + "--unknown7=unknown7value", + "--stringo=ovalue", + "--unknown8=unknown8value", + "--boole", "--unknown6", } want := []string{ @@ -435,6 +441,8 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { "boold", "true", "stringx", "1", "stringy", "ee", + "stringo", "ovalue", + "boole", "true", } got := []string{} store := func(flag *Flag, value string) error { From c8d792945dad66084b24d92a0fe5040406fe3d41 Mon Sep 17 00:00:00 2001 From: Rajat Jindal Date: Wed, 28 Mar 2018 10:33:08 -0700 Subject: [PATCH 4/5] handle --unknownflag=val arg and -u=val arg scenario --- flag.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/flag.go b/flag.go index c6954c66..c531a756 100644 --- a/flag.go +++ b/flag.go @@ -936,6 +936,12 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin f.usage() return a, ErrHelp case f.IgnoreUnknownFlags: + // --unknown=unknownval arg ... + // we do not want to lose arg in this case + if len(split) >= 2 { + return a, nil + } + return stripUnknownFlagValue(a), nil default: err = f.failf("unknown flag: --%s", name) @@ -984,11 +990,14 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse err = ErrHelp return case f.IgnoreUnknownFlags: - outArgs = stripUnknownFlagValue(outArgs) + // '-f=arg arg ...' + // we do not want to lose arg in this case if len(shorthands) > 2 && shorthands[1] == '=' { - // '-f=arg' outShorts = "" + return } + + outArgs = stripUnknownFlagValue(outArgs) return default: err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) From 724561935e0e8a6668ab03049996019949ac9952 Mon Sep 17 00:00:00 2001 From: Rajat Jindal Date: Thu, 29 Mar 2018 07:28:16 -0700 Subject: [PATCH 5/5] add ParseErrorsWhiteList to extend error handling during parsing --- flag.go | 14 ++++++++++---- flag_test.go | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/flag.go b/flag.go index c531a756..20ace82b 100644 --- a/flag.go +++ b/flag.go @@ -123,6 +123,12 @@ const ( PanicOnError ) +// 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 bool +} + // NormalizedName is a flag name that has been normalized according to rules // for the FlagSet (e.g. making '-' and '_' equivalent). type NormalizedName string @@ -138,8 +144,8 @@ type FlagSet struct { // help/usage messages. SortFlags bool - // IgnoreUnknownFlags is used to indicate to ignore unknown flags - IgnoreUnknownFlags bool + // ParseErrorsWhitelist is used to configure a whitelist of errors + ParseErrorsWhitelist ParseErrorsWhitelist name string parsed bool @@ -935,7 +941,7 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin case name == "help": f.usage() return a, ErrHelp - case f.IgnoreUnknownFlags: + case f.ParseErrorsWhitelist.UnknownFlags: // --unknown=unknownval arg ... // we do not want to lose arg in this case if len(split) >= 2 { @@ -989,7 +995,7 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse f.usage() err = ErrHelp return - case f.IgnoreUnknownFlags: + case f.ParseErrorsWhitelist.UnknownFlags: // '-f=arg arg ...' // we do not want to lose arg in this case if len(shorthands) > 2 && shorthands[1] == '=' { diff --git a/flag_test.go b/flag_test.go index e0047e69..3c13de39 100644 --- a/flag_test.go +++ b/flag_test.go @@ -398,7 +398,7 @@ func testParseWithUnknownFlags(f *FlagSet, t *testing.T) { if f.Parsed() { t.Error("f.Parse() = true before Parse") } - f.IgnoreUnknownFlags = true + f.ParseErrorsWhitelist.UnknownFlags = true f.BoolP("boola", "a", false, "bool value") f.BoolP("boolb", "b", false, "bool2 value")