Skip to content

Commit 9b981a0

Browse files
authored
Merge pull request #2425 from BenYang12/fix/preserve-equals-flag-whitespace
fix: preserve whitespace in equals-form flag values
2 parents 7fc6782 + fe4c1c7 commit 9b981a0

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

‎command_parse.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
147147
valFromEqual := false
148148
tracef("flagName:1 (fName=%[1]q)", flagName)
149149
if index := strings.Index(flagName, "="); index != -1 {
150-
flagVal = flagName[index+1:]
150+
// Classify the flag using the trimmed token, but leave value
151+
// whitespace handling to the flag's value parser.
152+
_, flagVal, _ = strings.Cut(rargs[0], "=")
151153
flagName = flagName[:index]
152154
valFromEqual = true
153155
}

‎command_test.go‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,91 @@ func TestCommand_PositionalArgsKeepWhitespace(t *testing.T) {
11721172
require.Equal(t, []string{" padded ", "\ttabbed\t"}, args.Slice())
11731173
}
11741174

1175+
func TestCommand_FlagValuesKeepWhitespace(t *testing.T) {
1176+
cases := []struct {
1177+
name string
1178+
flag string
1179+
value string
1180+
trimSpace bool
1181+
}{
1182+
{name: "empty", flag: "--text"},
1183+
{name: "spaces", flag: "--text", value: " hello "},
1184+
{name: "tab only", flag: "--text", value: "\t"},
1185+
{name: "newline", flag: "--text", value: "line\n"},
1186+
{name: "unicode space", flag: "--text", value: "space\u00a0"},
1187+
{name: "embedded equals", flag: "--text", value: "a=b "},
1188+
{name: "alias", flag: "-t", value: " hello "},
1189+
{name: "leading flag whitespace", flag: "\t --text", value: " hello "},
1190+
{name: "trim enabled", flag: "--text", value: "\t hello \n", trimSpace: true},
1191+
}
1192+
for _, kind := range []string{"string", "slice", "map"} {
1193+
for _, tc := range cases {
1194+
for _, equals := range []bool{false, true} {
1195+
t.Run(fmt.Sprintf("%s/%s/equals=%v", kind, tc.name, equals), func(t *testing.T) {
1196+
config := StringConfig{TrimSpace: tc.trimSpace}
1197+
want := tc.value
1198+
if tc.trimSpace {
1199+
want = strings.TrimSpace(want)
1200+
}
1201+
var fl Flag
1202+
var expected any
1203+
input := tc.value
1204+
switch kind {
1205+
case "string":
1206+
fl = &StringFlag{Name: "text", Aliases: []string{"t"}, Config: config}
1207+
expected = want
1208+
case "slice":
1209+
fl = &StringSliceFlag{Name: "text", Aliases: []string{"t"}, Config: config}
1210+
expected = []string{want}
1211+
case "map":
1212+
fl = &StringMapFlag{Name: "text", Aliases: []string{"t"}, Config: config}
1213+
input = "key=" + tc.value
1214+
expected = map[string]string{"key": want}
1215+
}
1216+
args := []string{"app", tc.flag, input, "operand"}
1217+
if equals {
1218+
args = []string{"app", tc.flag + "=" + input, "operand"}
1219+
}
1220+
cmd := &Command{Flags: []Flag{fl}, Action: func(_ context.Context, cmd *Command) error {
1221+
assert.Equal(t, expected, cmd.Value("text"))
1222+
assert.Equal(t, []string{"operand"}, cmd.Args().Slice())
1223+
return nil
1224+
}}
1225+
require.NoError(t, cmd.Run(buildTestContext(t), args))
1226+
})
1227+
}
1228+
}
1229+
}
1230+
}
1231+
1232+
func TestCommand_EqualsFlagValueValidation(t *testing.T) {
1233+
for _, kind := range []string{"int", "bool"} {
1234+
for _, whitespace := range []string{"", " ", "\t"} {
1235+
t.Run(fmt.Sprintf("%s/whitespace=%q", kind, whitespace), func(t *testing.T) {
1236+
var fl Flag = &IntFlag{Name: "value"}
1237+
value := "42"
1238+
if kind == "bool" {
1239+
fl = &BoolFlag{Name: "value"}
1240+
value = "true"
1241+
}
1242+
actionRan := false
1243+
cmd := &Command{
1244+
Flags: []Flag{fl}, Writer: io.Discard, ErrWriter: io.Discard,
1245+
Action: func(_ context.Context, _ *Command) error { actionRan = true; return nil },
1246+
}
1247+
err := cmd.Run(buildTestContext(t), []string{"app", "--value=" + value + whitespace})
1248+
if whitespace == "" {
1249+
require.NoError(t, err)
1250+
assert.True(t, actionRan)
1251+
} else {
1252+
require.Error(t, err)
1253+
assert.False(t, actionRan)
1254+
}
1255+
})
1256+
}
1257+
}
1258+
}
1259+
11751260
func TestCommand_CommandWithNoFlagBeforeTerminator(t *testing.T) {
11761261
var args Args
11771262

0 commit comments

Comments
 (0)