Skip to content

Commit

Permalink
Add additional validation to command name
Browse files Browse the repository at this point in the history
  • Loading branch information
mfridman committed Dec 30, 2024
1 parent af21dc2 commit 8831926
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 9 deletions.
26 changes: 19 additions & 7 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"errors"
"flag"
"fmt"
"regexp"
"slices"
"strconv"
"strings"

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

var validNameRegex = regexp.MustCompile(`^[a-zA-Z]+$`)

func validateName(root *Command) error {
if !validNameRegex.MatchString(root.Name) {
return fmt.Errorf("must contain only letters, no spaces or special characters")
}
return nil
}

func validateCommands(root *Command, path []string) error {
if root.Name == "" {
if len(path) == 0 {
return errors.New("root command has no name")
}
return fmt.Errorf("subcommand in path %q has no name", strings.Join(path, " "))
}
// Ensure name has no spaces
if strings.Contains(root.Name, " ") {
return fmt.Errorf("command name %q contains spaces, must be a single word", root.Name)
return fmt.Errorf("subcommand in path [%s] has no name", strings.Join(path, ", "))
}

// Add current command to path for nested validation
currentPath := append(path, root.Name)
if err := validateName(root); err != nil {
quoted := make([]string, len(currentPath))
for i, p := range currentPath {
quoted[i] = strconv.Quote(p)
}
return fmt.Errorf("command [%s]: %w", strings.Join(quoted, ", "), err)
}

// Recursively validate all subcommands
for _, sub := range root.SubCommands {
if err := validateCommands(sub, currentPath); err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func TestParse(t *testing.T) {

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

0 comments on commit 8831926

Please sign in to comment.