From 77c71bd5962368b931e479123a155e825485f119 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 17 Sep 2015 16:44:31 -0700 Subject: [PATCH] Add trie to verify wrong inputs, and provide meaningful messages --- commands.go | 4 ++ main.go | 18 ++++++++- trie.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 trie.go diff --git a/commands.go b/commands.go index f3618673b..d03a693f7 100644 --- a/commands.go +++ b/commands.go @@ -21,7 +21,11 @@ import "github.com/minio/cli" // Collection of minio commands currently supported are var commands = []cli.Command{} +// Collection of minio commands currently supported in a trie tree +var commandsTree = newTrie() + // registerCommand registers a cli command func registerCommand(command cli.Command) { commands = append(commands, command) + commandsTree.Insert(command.Name) } diff --git a/main.go b/main.go index c69a0ea92..a1d91c93d 100644 --- a/main.go +++ b/main.go @@ -87,6 +87,14 @@ func getFormattedVersion() string { return t.Format(http.TimeFormat) } +func findClosestCommands(command string) []string { + var closestCommands []string + for _, value := range commandsTree.PrefixMatch(command) { + closestCommands = append(closestCommands, value.(string)) + } + return closestCommands +} + func registerApp() *cli.App { // register all commands registerCommand(donutCmd) @@ -132,7 +140,15 @@ VERSION: {{end}} ` app.CommandNotFound = func(ctx *cli.Context, command string) { - Fatalf("Command not found: ‘%s’\n", command) + msg := fmt.Sprintf("‘%s’ is not a mc command. See ‘minio help’.", command) + closestCommands := findClosestCommands(command) + if len(closestCommands) > 0 { + msg += fmt.Sprintf("\n\nDid you mean one of these?\n") + for _, cmd := range closestCommands { + msg += fmt.Sprintf(" ‘%s’\n", cmd) + } + } + Fatalln(msg) } return app diff --git a/trie.go b/trie.go new file mode 100644 index 000000000..844458aa7 --- /dev/null +++ b/trie.go @@ -0,0 +1,112 @@ +/* + * Minio Cloud Storage, (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +// This package borrows idea from - https://godoc.org/golang.org/x/text/internal/triegen + +// Trie trie container +type Trie struct { + root *trieNode + size int +} + +// newTrie get new trie +func newTrie() *Trie { + return &Trie{ + root: newTrieNode(), + size: 0, + } +} + +// trieNode trie tree node container carries value and children +type trieNode struct { + exists bool + value interface{} + child map[rune]*trieNode // runes as child +} + +func newTrieNode() *trieNode { + return &trieNode{ + exists: false, + value: nil, + child: make(map[rune]*trieNode), + } +} + +// Insert insert a key +func (t *Trie) Insert(key string) { + curNode := t.root + for _, v := range key { + if curNode.child[v] == nil { + curNode.child[v] = newTrieNode() + } + curNode = curNode.child[v] + } + + if !curNode.exists { + // increment when new rune child is added + t.size++ + curNode.exists = true + } + // value is stored for retrieval in future + curNode.value = key +} + +// PrefixMatch - prefix match +func (t *Trie) PrefixMatch(key string) []interface{} { + node, _ := t.findNode(key) + if node != nil { + return t.walk(node) + } + return []interface{}{} +} + +// walk the tree +func (t *Trie) walk(node *trieNode) (ret []interface{}) { + if node.exists { + ret = append(ret, node.value) + } + for _, v := range node.child { + ret = append(ret, t.walk(v)...) + } + return +} + +// find nodes corresponding to key +func (t *Trie) findNode(key string) (node *trieNode, index int) { + curNode := t.root + f := false + for k, v := range key { + if f { + index = k + f = false + } + if curNode.child[v] == nil { + return nil, index + } + curNode = curNode.child[v] + if curNode.exists { + f = true + } + } + + if curNode.exists { + index = len(key) + } + + return curNode, index +}