Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Option to allow passing value to bool flag #367

Merged
merged 1 commit into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
102 changes: 91 additions & 11 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"},
Expand Down Expand Up @@ -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)
}
}
}
}
Expand Down Expand Up @@ -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)
}
}
}
}