|
4 | 4 | "errors" |
5 | 5 | "flag" |
6 | 6 | "fmt" |
| 7 | + "regexp" |
7 | 8 | "slices" |
| 9 | + "strconv" |
8 | 10 | "strings" |
9 | 11 |
|
10 | 12 | "github.com/mfridman/xflag" |
@@ -178,22 +180,32 @@ func Parse(root *Command, args []string) error { |
178 | 180 | return nil |
179 | 181 | } |
180 | 182 |
|
| 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 | + |
181 | 192 | func validateCommands(root *Command, path []string) error { |
182 | 193 | if root.Name == "" { |
183 | 194 | if len(path) == 0 { |
184 | 195 | return errors.New("root command has no name") |
185 | 196 | } |
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, ", ")) |
191 | 198 | } |
192 | 199 |
|
193 | | - // Add current command to path for nested validation |
194 | 200 | 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 | + } |
195 | 208 |
|
196 | | - // Recursively validate all subcommands |
197 | 209 | for _, sub := range root.SubCommands { |
198 | 210 | if err := validateCommands(sub, currentPath); err != nil { |
199 | 211 | return err |
|
0 commit comments