diff --git a/parser.go b/parser.go index 738f568..18bd2e1 100644 --- a/parser.go +++ b/parser.go @@ -113,6 +113,10 @@ const ( // POSIX processing. PassAfterNonOption + // AllowBoolValues allows a user to assign true/false to a boolean value + // rather than raising an error stating it cannot have an argument. + AllowBoolValues + // Default is a convenient default set of options which should cover // most of the uses of the flags package. Default = HelpFlag | PrintErrors | PassDoubleDash @@ -521,11 +525,10 @@ func (p *parseState) estimateCommand() error { func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) { if !option.canArgument() { - if argument != nil { + if argument != nil && (p.Options&AllowBoolValues) == None { return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option) } - - err = option.Set(nil) + err = option.Set(argument) } else if argument != nil || (canarg && !s.eof()) { var arg string diff --git a/parser_test.go b/parser_test.go index 5700bcd..9a951a0 100644 --- a/parser_test.go +++ b/parser_test.go @@ -37,9 +37,10 @@ type defaultOptions struct { func TestDefaults(t *testing.T) { var tests = []struct { - msg string - args []string - expected defaultOptions + msg string + args []string + expected defaultOptions + expectedErr string }{ { msg: "no arguments, expecting default values", @@ -91,6 +92,11 @@ func TestDefaults(t *testing.T) { SliceDefault: []int{3}, }, }, + { + msg: "non-zero value arguments, expecting overwritten arguments", + args: []string{"-3=true"}, + expectedErr: "bool flag `-3' cannot have an argument", + }, { msg: "zero value arguments, expecting overwritten arguments", args: []string{"--i=0", "--id=0", "--f=0", "--fd=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"}, @@ -120,16 +126,24 @@ func TestDefaults(t *testing.T) { var opts defaultOptions _, err := ParseArgs(&opts, test.args) - if err != nil { - t.Fatalf("%s:\nUnexpected error: %v", test.msg, err) - } + if test.expectedErr != "" { + if err == nil { + t.Errorf("%s:\nExpected error containing substring %q", test.msg, test.expectedErr) + } else if !strings.Contains(err.Error(), test.expectedErr) { + t.Errorf("%s:\nExpected error %q to contain substring %q", test.msg, err, test.expectedErr) + } + } else { + if err != nil { + t.Fatalf("%s:\nUnexpected error: %v", test.msg, err) + } - if opts.Slice == nil { - opts.Slice = []int{} - } + if opts.Slice == nil { + opts.Slice = []int{} + } - if !reflect.DeepEqual(opts, test.expected) { - t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts) + if !reflect.DeepEqual(opts, test.expected) { + t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts) + } } } } @@ -680,3 +694,69 @@ func TestCommandHandler(t *testing.T) { assertStringArray(t, executedArgs, []string{"arg1", "arg2"}) } + +func TestAllowBoolValues(t *testing.T) { + var tests = []struct { + msg string + args []string + expectedErr string + expected bool + expectedNonOptArgs []string + }{ + { + msg: "no value", + args: []string{"-v"}, + expected: true, + }, + { + msg: "true value", + args: []string{"-v=true"}, + expected: true, + }, + { + msg: "false value", + args: []string{"-v=false"}, + expected: false, + }, + { + msg: "bad value", + args: []string{"-v=badvalue"}, + expectedErr: `parsing "badvalue": invalid syntax`, + }, + { + // this test is to ensure flag values can only be specified as --flag=value and not "--flag value". + // if "--flag value" was supported it's not clear if value should be a non-optional argument + // or the value for the flag. + msg: "validate flags can only be set with a value immediately following an assignment operator (=)", + args: []string{"-v", "false"}, + expected: true, + expectedNonOptArgs: []string{"false"}, + }, + } + + for _, test := range tests { + var opts = struct { + Value bool `short:"v"` + }{} + parser := NewParser(&opts, AllowBoolValues) + nonOptArgs, err := parser.ParseArgs(test.args) + + if test.expectedErr == "" { + if err != nil { + t.Fatalf("%s:\nUnexpected parse error: %s", test.msg, err) + } + if opts.Value != test.expected { + t.Errorf("%s:\nExpected %v; got %v", test.msg, test.expected, opts.Value) + } + if len(test.expectedNonOptArgs) != len(nonOptArgs) && !reflect.DeepEqual(test.expectedNonOptArgs, nonOptArgs) { + t.Errorf("%s:\nUnexpected non-argument options\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.expectedNonOptArgs, nonOptArgs) + } + } else { + if err == nil { + t.Errorf("%s:\nExpected error containing substring %q", test.msg, test.expectedErr) + } else if !strings.Contains(err.Error(), test.expectedErr) { + t.Errorf("%s:\nExpected error %q to contain substring %q", test.msg, err, test.expectedErr) + } + } + } +}