Skip to content
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
15 changes: 8 additions & 7 deletions command_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,19 +236,20 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
// the check above has already found every flag of the group
sf := cmd.lookupAppliedFlag(string(c))
if fb, ok := sf.(boolFlag); ok && fb.IsBoolFlag() {
fv := flagVal
if index == lastIndex && flagVal == "" {
fv = "true"
}
if fv == "" {
fv = "true"
// the value after "=" belongs to the last flag of the group,
// so a bool flag before it is just set
fv := "true"
if index == lastIndex && flagVal != "" {
fv = flagVal
}
if err := cmd.set(string(c), sf, fv); err != nil {
tracef("processing flag.2 (fName=%[1]q)", string(c))
return &stringSliceArgs{posArgs}, err
}
} else if index == lastIndex { // last flag can take an arg
if flagVal == "" {
// as with a single flag, an explicit "=" with nothing after
// it is an empty value, not a request to read the next arg
if flagVal == "" && !valFromEqual {
if len(rargs) == 1 {
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c))
}
Expand Down
77 changes: 77 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,41 @@ func TestCommand_UseShortOptionHandlingMultiByteLastFlag(t *testing.T) {
assert.EqualError(t, err, "flag needs an argument: "+name)
}

func TestCommand_UseShortOptionHandlingValueForLastFlag(t *testing.T) {
var verbose, extra bool
var value string
var args []string
newCmd := func() *Command {
return &Command{
Name: "app",
UseShortOptionHandling: true,
Flags: []Flag{
&BoolFlag{Name: "v"},
&BoolFlag{Name: "x"},
&StringFlag{Name: "n"},
},
Action: func(_ context.Context, cmd *Command) error {
verbose = cmd.Bool("v")
extra = cmd.Bool("x")
value = cmd.String("n")
args = cmd.Args().Slice()
return nil
},
Writer: io.Discard,
ErrWriter: io.Discard,
}
}

require.NoError(t, newCmd().Run(buildTestContext(t), []string{"app", "-vn=value", "operand"}))
assert.True(t, verbose)
assert.Equal(t, "value", value)
assert.Equal(t, []string{"operand"}, args)

require.NoError(t, newCmd().Run(buildTestContext(t), []string{"app", "-vx=false"}))
assert.True(t, verbose)
assert.False(t, extra)
}

func TestCommand_UseShortOptionHandlingCommand(t *testing.T) {
var (
one, two bool
Expand Down Expand Up @@ -6948,6 +6983,48 @@ func TestFlagEqualsEmptyValue(t *testing.T) {
assert.Equal(t, "", val)
assert.Equal(t, []string{"positional"}, args)
})

t.Run("-vn= in a short option group sets empty string", func(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼 love it

var val string
var verbose bool
var args []string

cmd := &Command{
UseShortOptionHandling: true,
Flags: []Flag{
&BoolFlag{Name: "v", Destination: &verbose},
&StringFlag{Name: "n", Destination: &val, Value: "default"},
},
Action: func(_ context.Context, cmd *Command) error {
args = cmd.Args().Slice()
return nil
},
}

err := cmd.Run(buildTestContext(t), []string{"app", "-vn=", "positional"})
assert.NoError(t, err)
assert.True(t, verbose)
assert.Equal(t, "", val)
assert.Equal(t, []string{"positional"}, args)
})

t.Run("-vn= at the end of a short option group sets empty string", func(t *testing.T) {
var val string
var verbose bool

cmd := &Command{
UseShortOptionHandling: true,
Flags: []Flag{
&BoolFlag{Name: "v", Destination: &verbose},
&StringFlag{Name: "n", Destination: &val, Value: "default"},
},
}

err := cmd.Run(buildTestContext(t), []string{"app", "-vn="})
assert.NoError(t, err)
assert.True(t, verbose)
assert.Equal(t, "", val)
})
}

// TestCommand_NoDefaultCmdArgMatchingFlag tests the argument set
Expand Down
Loading