Skip to content

Commit d1d8108

Browse files
authored
Merge pull request #2438 from SulimanAbdulrazzaq/fix/short-option-multibyte-last-flag
fix: handle a multi-byte flag at the end of a short option group
2 parents 19f0644 + f3bcf41 commit d1d8108

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

‎command_parse.go‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
212212
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
213213
}
214214

215+
// ranging over flagName yields byte offsets, so the last flag starts
216+
// where its rune starts, which is not len-1 for a multi-byte rune
217+
_, lastSize := utf8.DecodeLastRuneInString(flagName)
218+
lastIndex := len(flagName) - lastSize
219+
215220
// try to split the flags
216221
for index, c := range flagName {
217222
tracef("processing flag (fName=%[1]q)", string(c))
@@ -223,7 +228,7 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
223228
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
224229
} else if fb, ok := sf.(boolFlag); ok && fb.IsBoolFlag() {
225230
fv := flagVal
226-
if index == (len(flagName)-1) && flagVal == "" {
231+
if index == lastIndex && flagVal == "" {
227232
fv = "true"
228233
}
229234
if fv == "" {
@@ -233,7 +238,7 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
233238
tracef("processing flag.2 (fName=%[1]q)", string(c))
234239
return &stringSliceArgs{posArgs}, err
235240
}
236-
} else if index == len(flagName)-1 { // last flag can take an arg
241+
} else if index == lastIndex { // last flag can take an arg
237242
if flagVal == "" {
238243
if len(rargs) == 1 {
239244
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c))

‎command_test.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,40 @@ func TestCommand_UseShortOptionHandling_missing_value(t *testing.T) {
14941494
assert.EqualError(t, err, "flag needs an argument: -n")
14951495
}
14961496

1497+
func TestCommand_UseShortOptionHandlingMultiByteLastFlag(t *testing.T) {
1498+
const name = "ש"
1499+
1500+
var verbose bool
1501+
var value string
1502+
var args []string
1503+
newCmd := func() *Command {
1504+
return &Command{
1505+
Name: "app",
1506+
UseShortOptionHandling: true,
1507+
Flags: []Flag{
1508+
&BoolFlag{Name: "v"},
1509+
&StringFlag{Name: name},
1510+
},
1511+
Action: func(_ context.Context, cmd *Command) error {
1512+
verbose = cmd.Bool("v")
1513+
value = cmd.String(name)
1514+
args = cmd.Args().Slice()
1515+
return nil
1516+
},
1517+
Writer: io.Discard,
1518+
ErrWriter: io.Discard,
1519+
}
1520+
}
1521+
1522+
require.NoError(t, newCmd().Run(buildTestContext(t), []string{"app", "-v" + name, "value", "operand"}))
1523+
assert.True(t, verbose)
1524+
assert.Equal(t, "value", value)
1525+
assert.Equal(t, []string{"operand"}, args)
1526+
1527+
err := newCmd().Run(buildTestContext(t), []string{"app", "-v" + name})
1528+
assert.EqualError(t, err, "flag needs an argument: "+name)
1529+
}
1530+
14971531
func TestCommand_UseShortOptionHandlingCommand(t *testing.T) {
14981532
var (
14991533
one, two bool

0 commit comments

Comments
 (0)