Skip to content

Commit 8831926

Browse files
committed
Add additional validation to command name
1 parent af21dc2 commit 8831926

File tree

2 files changed

+21
-9
lines changed

2 files changed

+21
-9
lines changed

parse.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"errors"
55
"flag"
66
"fmt"
7+
"regexp"
78
"slices"
9+
"strconv"
810
"strings"
911

1012
"github.com/mfridman/xflag"
@@ -178,22 +180,32 @@ func Parse(root *Command, args []string) error {
178180
return nil
179181
}
180182

183+
var validNameRegex = regexp.MustCompile(`^[a-zA-Z]+$`)
184+
185+
func validateName(root *Command) error {
186+
if !validNameRegex.MatchString(root.Name) {
187+
return fmt.Errorf("must contain only letters, no spaces or special characters")
188+
}
189+
return nil
190+
}
191+
181192
func validateCommands(root *Command, path []string) error {
182193
if root.Name == "" {
183194
if len(path) == 0 {
184195
return errors.New("root command has no name")
185196
}
186-
return fmt.Errorf("subcommand in path %q has no name", strings.Join(path, " "))
187-
}
188-
// Ensure name has no spaces
189-
if strings.Contains(root.Name, " ") {
190-
return fmt.Errorf("command name %q contains spaces, must be a single word", root.Name)
197+
return fmt.Errorf("subcommand in path [%s] has no name", strings.Join(path, ", "))
191198
}
192199

193-
// Add current command to path for nested validation
194200
currentPath := append(path, root.Name)
201+
if err := validateName(root); err != nil {
202+
quoted := make([]string, len(currentPath))
203+
for i, p := range currentPath {
204+
quoted[i] = strconv.Quote(p)
205+
}
206+
return fmt.Errorf("command [%s]: %w", strings.Join(quoted, ", "), err)
207+
}
195208

196-
// Recursively validate all subcommands
197209
for _, sub := range root.SubCommands {
198210
if err := validateCommands(sub, currentPath); err != nil {
199211
return err

parse_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ func TestParse(t *testing.T) {
308308

309309
err := Parse(s.root, nil)
310310
require.Error(t, err)
311-
require.ErrorContains(t, err, `subcommand in path "todo nested" has no name`)
311+
require.ErrorContains(t, err, `subcommand in path [todo, nested] has no name`)
312312
})
313313
t.Run("required flag", func(t *testing.T) {
314314
t.Parallel()
@@ -368,6 +368,6 @@ func TestParse(t *testing.T) {
368368
}
369369
err := Parse(cmd, nil)
370370
require.Error(t, err)
371-
require.ErrorContains(t, err, `command name "sub command" contains spaces, must be a single word`)
371+
require.ErrorContains(t, err, `failed to parse: command ["root", "sub command"]: must contain only letters, no spaces or special characters`)
372372
})
373373
}

0 commit comments

Comments
 (0)