diff --git a/cmd/root.go b/cmd/root.go index cc92a978..731b3cc8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -54,16 +54,16 @@ func init() { extension.Register(rootCmd) account.Register(rootCmd, func(commandName string) (*account.ServiceContainer, error) { if commandName == "login" || commandName == "logout" { - return &account.ServiceContainer{ - AccountClient: nil, - }, nil + return &account.ServiceContainer{AccountClient: nil}, nil } + client, err := accountApi.NewApi(rootCmd.Context()) if err != nil { return nil, err } - return &account.ServiceContainer{ - AccountClient: client, - }, nil + + return &account.ServiceContainer{AccountClient: client}, nil }) + + applyCommandShortcuts(rootCmd) } diff --git a/cmd/shortcut.go b/cmd/shortcut.go new file mode 100644 index 00000000..35d289f0 --- /dev/null +++ b/cmd/shortcut.go @@ -0,0 +1,88 @@ +package cmd + +import ( + "strings" + "unicode" + + "github.com/spf13/cobra" +) + +func applyCommandShortcuts(root *cobra.Command) { + assignAliases(root) +} + +func assignAliases(parent *cobra.Command) { + children := parent.Commands() + used := map[string]struct{}{} + + for _, child := range children { + for _, alias := range child.Aliases { + used[alias] = struct{}{} + } + } + + for _, child := range children { + for _, candidate := range aliasCandidates(child.Name()) { + if candidate == "" || candidate == child.Name() { + continue + } + if contains(child.Aliases, candidate) { + used[candidate] = struct{}{} + break + } + if _, exists := used[candidate]; exists { + continue + } + + child.Aliases = append(child.Aliases, candidate) + used[candidate] = struct{}{} + break + } + + assignAliases(child) + } +} + +func contains(values []string, wanted string) bool { + for _, value := range values { + if value == wanted { + return true + } + } + + return false +} + +func aliasCandidates(name string) []string { + parts := strings.FieldsFunc(strings.ToLower(name), func(r rune) bool { + return !unicode.IsLetter(r) && !unicode.IsNumber(r) + }) + if len(parts) == 0 { + return nil + } + + compact := strings.Join(parts, "") + seen := map[string]struct{}{} + candidates := make([]string, 0, len(compact)+1) + + if len(parts) > 1 { + initials := make([]byte, 0, len(parts)) + for _, part := range parts { + initials = append(initials, part[0]) + } + candidate := string(initials) + seen[candidate] = struct{}{} + candidates = append(candidates, candidate) + } + + for i := 1; i <= len(compact); i++ { + candidate := compact[:i] + if _, exists := seen[candidate]; exists { + continue + } + seen[candidate] = struct{}{} + candidates = append(candidates, candidate) + } + + return candidates +} diff --git a/cmd/shortcut_test.go b/cmd/shortcut_test.go new file mode 100644 index 00000000..21fe3349 --- /dev/null +++ b/cmd/shortcut_test.go @@ -0,0 +1,33 @@ +package cmd + +import "testing" + +func TestRootCommandShortcuts(t *testing.T) { + tests := []struct { + args []string + want string + }{ + {args: []string{"p"}, want: "project"}, + {args: []string{"e"}, want: "extension"}, + {args: []string{"a"}, want: "account"}, + {args: []string{"p", "e", "l"}, want: "list"}, + {args: []string{"p", "co"}, want: "config"}, + {args: []string{"p", "cc"}, want: "clear-cache"}, + {args: []string{"p", "aw"}, want: "admin-watch"}, + {args: []string{"e", "gv"}, want: "get-version"}, + {args: []string{"a", "p", "e", "i", "p"}, want: "pull"}, + } + + for _, tt := range tests { + command, _, err := rootCmd.Find(tt.args) + if err != nil { + t.Fatalf("Find(%v) returned error: %v", tt.args, err) + } + if command == nil { + t.Fatalf("Find(%v) returned no command", tt.args) + } + if command.Name() != tt.want { + t.Fatalf("Find(%v) resolved to %q, want %q", tt.args, command.Name(), tt.want) + } + } +}