Skip to content

Commit

Permalink
add ability to ignore unknown flags (#160)
Browse files Browse the repository at this point in the history
* add ability to ignore unknown flags

* add testcases

* add 2 more patterns to the testcase

* handle --unknownflag=val arg and -u=val arg scenario

* add ParseErrorsWhiteList to extend error handling during parsing
  • Loading branch information
rajatjindal authored and eparis committed Mar 30, 2018
1 parent ad68c28 commit 1cd4a0c
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 7 deletions.
63 changes: 57 additions & 6 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -138,6 +144,9 @@ type FlagSet struct {
// help/usage messages.
SortFlags bool

// ParseErrorsWhitelist is used to configure a whitelist of errors
ParseErrorsWhitelist ParseErrorsWhitelist

name string
parsed bool
actual map[NormalizedName]*Flag
Expand Down Expand Up @@ -899,6 +908,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:]
Expand All @@ -910,13 +938,24 @@ 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.ParseErrorsWhitelist.UnknownFlags:
// --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)
return
}
err = f.failf("unknown flag: --%s", name)
return
}

var value string
Expand Down Expand Up @@ -954,13 +993,25 @@ 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.ParseErrorsWhitelist.UnknownFlags:
// '-f=arg arg ...'
// we do not want to lose arg in this case
if len(shorthands) > 2 && shorthands[1] == '=' {
outShorts = ""
return
}

outArgs = stripUnknownFlagValue(outArgs)
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
Expand Down
78 changes: 77 additions & 1 deletion flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,78 @@ 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)
}
}

func testParseWithUnknownFlags(f *FlagSet, t *testing.T) {
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
f.ParseErrorsWhitelist.UnknownFlags = 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.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",
"-cs=xx",
"--stringz=something",
"--unknown1",
"unknown1Value",
"-d=true",
"-x",
"--unknown2=unknown2Value",
"-u=unknown3Value",
"-p",
"unknown4Value",
"-q", //another unknown with bool value
"-y",
"ee",
"--unknown7=unknown7value",
"--stringo=ovalue",
"--unknown8=unknown8value",
"--boole",
"--unknown6",
}
want := []string{
"boola", "true",
"boolb", "true",
"boolc", "true",
"stringa", "xx",
"stringz", "something",
"boold", "true",
"stringx", "1",
"stringy", "ee",
"stringo", "ovalue",
"boole", "true",
}
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)
}
}
Expand Down Expand Up @@ -500,6 +571,11 @@ func TestParseAll(t *testing.T) {
testParseAll(GetCommandLine(), t)
}

func TestIgnoreUnknownFlags(t *testing.T) {
ResetForTesting(func() { t.Error("bad parse") })
testParseWithUnknownFlags(GetCommandLine(), t)
}

func TestFlagSetParse(t *testing.T) {
testParse(NewFlagSet("test", ContinueOnError), t)
}
Expand Down

0 comments on commit 1cd4a0c

Please sign in to comment.