diff --git a/cmd/main.go b/cmd/main.go index 231f5446ca94f..fbc5a4c3a541c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,6 +13,7 @@ import ( apiserver "github.com/argoproj/argo-cd/cmd/argocd-server/commands" util "github.com/argoproj/argo-cd/cmd/argocd-util/commands" cli "github.com/argoproj/argo-cd/cmd/argocd/commands" + cmdutil "github.com/argoproj/argo-cd/cmd/util" ) const ( @@ -27,6 +28,8 @@ func main() { binaryName = val } switch binaryName { + case "argocd", "argocd-linux-amd64", "argocd-darwin-amd64", "argocd-windows-amd64.exe": + command = cli.NewCommand() case "argocd-util", "argocd-util-linux-amd64", "argocd-util-darwin-amd64", "argocd-util-windows-amd64.exe": command = util.NewCommand() case "argocd-server": @@ -38,6 +41,10 @@ func main() { case "argocd-dex": command = dex.NewCommand() default: + if suggestionsString := cmdutil.FindSuggestions(binaryName); suggestionsString != "" { + fmt.Printf("unknown command %q for %s\n", binaryName, suggestionsString) + os.Exit(1) + } // default is argocd cli command = cli.NewCommand() } diff --git a/cmd/util/suggest.go b/cmd/util/suggest.go new file mode 100644 index 0000000000000..9bbdfd6e61ce3 --- /dev/null +++ b/cmd/util/suggest.go @@ -0,0 +1,82 @@ +package util + +import ( + "fmt" + "strings" +) + +const ( + minSuggestionsDistance = 3 +) + +var ( + argocdCommands = []string{"argocd-util", "argocd-server", "argocd-application-controller", "argocd-repo-server", "argocd-dex"} +) + +func FindSuggestions(binaryName string) string { + suggestionsString := "" + if suggestions := SuggestionsFor(binaryName); len(suggestions) > 0 { + suggestionsString += "\n\nDid you mean this?\n" + for _, s := range suggestions { + suggestionsString += fmt.Sprintf("\t%v\n", s) + } + } + return suggestionsString +} + +// Logic from https://github.com/spf13/cobra/blob/master/command.go#L721 +// Modified suggestion by prefix to suggest when either binary Name is +// prefix to command name or command name is prefix to binary name +// Added suggestions by substring +// SuggestionsFor provides suggestions for the binary name. +func SuggestionsFor(binaryName string) []string { + var suggestions []string + for _, cmdName := range argocdCommands { + levenshteinDistance := ld(binaryName, cmdName, true) + suggestByLevenshtein := levenshteinDistance <= minSuggestionsDistance + suggestByPrefix := strings.HasPrefix(strings.ToLower(cmdName), strings.ToLower(binaryName)) || + strings.HasPrefix(strings.ToLower(binaryName), strings.ToLower(cmdName)) + suggestBySubstring := strings.Contains(cmdName, binaryName) + if suggestByLevenshtein || suggestByPrefix || suggestBySubstring { + suggestions = append(suggestions, cmdName) + } + } + return suggestions +} + +// Logic from https://github.com/spf13/cobra/blob/master/cobra.go#L165 +// ld compares two strings and returns the levenshtein distance between them. +func ld(s, t string, ignoreCase bool) int { + if ignoreCase { + s = strings.ToLower(s) + t = strings.ToLower(t) + } + d := make([][]int, len(s)+1) + for i := range d { + d[i] = make([]int, len(t)+1) + } + for i := range d { + d[i][0] = i + } + for j := range d[0] { + d[0][j] = j + } + for j := 1; j <= len(t); j++ { + for i := 1; i <= len(s); i++ { + if s[i-1] == t[j-1] { + d[i][j] = d[i-1][j-1] + } else { + min := d[i-1][j] + if d[i][j-1] < min { + min = d[i][j-1] + } + if d[i-1][j-1] < min { + min = d[i-1][j-1] + } + d[i][j] = min + 1 + } + } + + } + return d[len(s)][len(t)] +}