Skip to content

Commit

Permalink
Better error message on unrecognized command
Browse files Browse the repository at this point in the history
Closes issue #1436

License: MIT
Signed-off-by: Shaun Bruce <[email protected]>
  • Loading branch information
sbruce committed Jul 28, 2015
1 parent 075e687 commit c175700
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 3 deletions.
4 changes: 4 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions commands/cli/cmd_suggestion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cli

import (
"sort"
"strings"

levenshtein "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/texttheater/golang-levenshtein/levenshtein"
cmds "github.com/ipfs/go-ipfs/commands"
)

// Make a custom slice that can be sorted by its levenshtein value
type suggestionSlice []*suggestion

type suggestion struct {
cmd string
levenshtein int
}

func (s suggestionSlice) Len() int {
return len(s)
}

func (s suggestionSlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func (s suggestionSlice) Less(i, j int) bool {
return s[i].levenshtein < s[j].levenshtein
}

func suggestUnknownCmd(args []string, root *cmds.Command) []string {
arg := args[0]
var suggestions []string
sortableSuggestions := make(suggestionSlice, 0)
var sFinal []string
const MIN_LEVENSHTEIN = 3

var options levenshtein.Options = levenshtein.Options{
InsCost: 1,
DelCost: 3,
SubCost: 2,
Matches: func(sourceCharacter rune, targetCharacter rune) bool {
return sourceCharacter == targetCharacter
},
}

// Start with a simple strings.Contains check
for name, _ := range root.Subcommands {
if strings.Contains(arg, name) {
suggestions = append(suggestions, name)
}
}

// If the string compare returns a match, return
if len(suggestions) > 0 {
return suggestions
}

for name, _ := range root.Subcommands {
lev := levenshtein.DistanceForStrings([]rune(arg), []rune(name), options)
if lev <= MIN_LEVENSHTEIN {
sortableSuggestions = append(sortableSuggestions, &suggestion{name, lev})
}
}
sort.Sort(sortableSuggestions)

for _, j := range sortableSuggestions {
sFinal = append(sFinal, j.cmd)
}
return sFinal
}
14 changes: 11 additions & 3 deletions commands/cli/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c
}
}

stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive)
stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive, root)
if err != nil {
return req, cmd, path, err
}
Expand Down Expand Up @@ -196,7 +196,7 @@ func parseOpts(args []string, root *cmds.Command) (
return
}

func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []files.File, error) {
func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool, root *cmds.Command) ([]string, []files.File, error) {
// ignore stdin on Windows
if runtime.GOOS == "windows" {
stdin = nil
Expand Down Expand Up @@ -231,7 +231,15 @@ func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursi
// and the last arg definition is not variadic (or there are no definitions), return an error
notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
if notVariadic && len(inputs) > len(argDefs) {
return nil, nil, fmt.Errorf("Expected %v arguments, got %v: %v", len(argDefs), len(inputs), inputs)
suggestions := suggestUnknownCmd(inputs, root)

if len(suggestions) > 1 {
return nil, nil, fmt.Errorf("Unknown Command \"%s\"\n\nDid you mean any of these?\n\n\t%s", inputs[0], strings.Join(suggestions, "\n\t"))
} else if len(suggestions) > 0 {
return nil, nil, fmt.Errorf("Unknown Command \"%s\"\n\nDid you mean this?\n\n\t%s", inputs[0], suggestions[0])
} else {
return nil, nil, fmt.Errorf("Unknown Command \"%s\"\n", inputs[0])
}
}

stringArgs := make([]string, 0, numInputs)
Expand Down
43 changes: 43 additions & 0 deletions test/sharness/t0150-clisuggest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/sh

test_description="Test ipfs cli cmd suggest"

. lib/test-lib.sh

test_suggest() {


test_expect_success "test command fails" '
test_must_fail ipfs kog 2>actual
'

test_expect_success "test one command is suggested" '
grep "Did you mean this?" actual &&
grep "log" actual ||
test_fsh cat actual
'

test_expect_success "test command fails" '
test_must_fail ipfs lis 2>actual
'

test_expect_success "test multiple commands are suggested" '
grep "Did you mean any of these?" actual &&
grep "ls" actual &&
grep "id" actual ||
test_fsh cat actual
'

}

test_init_ipfs

test_suggest

test_launch_ipfs_daemon

test_suggest

test_kill_ipfs_daemon

test_done

0 comments on commit c175700

Please sign in to comment.