-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcmdtree.go
124 lines (99 loc) · 3.29 KB
/
cmdtree.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package cmdtree
import (
"bytes"
"fmt"
"io"
"strings"
)
// CommandExecutor is a function which is run when a command is
// resolved.
type CommandExecutor func(args string) error
// CommandExtender represents something that can extend a Command.
type CommandExtender interface {
// SubCmd adds a sub-command to this command.
SubCmd(trigger string, executor CommandExecutor) Command
}
// Command represents a command that can be executed or extended with
// sub-commands.
type Command interface {
// CommandExtender ensures a Command can extend itself.
CommandExtender
// Execute parses the input given with the delimiter defined when
// constructing the command. It then executes the command or
// sub-command found, and returns any errors.
Execute(input string) error
// String returns the command tree in a human readable format.
String() string
}
// Root creates a new command with no top-level commands. This is
// useful when you have multiple top-level commands.
func Root(delimiter string) Command {
return NewCmd(delimiter, "", nil)
}
// NewCmd creates a new command with a top-level command filled out.
// This is useful for when you are going to extend this command.
func NewCmd(subCmdDelim, trigger string, executor CommandExecutor) Command {
return &command{
delimiter: subCmdDelim,
trigger: trigger,
executor: executor,
}
}
type command struct {
delimiter, trigger string
subCmds []*command
executor CommandExecutor
}
// SubCmd implements SubCmd on the CommandExtender interface.
func (tree *command) SubCmd(trigger string, executor CommandExecutor) Command {
subCmd := &command{delimiter: tree.delimiter, trigger: trigger, executor: executor}
tree.subCmds = append(tree.subCmds, subCmd)
return subCmd
}
// Execute implements Execute on the Command interface.
func (tree *command) Execute(input string) error {
cmdStrs := strings.Split(input, tree.delimiter)
if tree.trigger != "" {
if cmdStrs[0] != tree.trigger {
// It all starts here. Need to at least match self.
return fmt.Errorf(`could not match command "%s"`, input)
} else if len(tree.subCmds) == 0 || len(cmdStrs) == 1 {
// No sub-commands, or no requested sub-commands; just
// execute self.
if tree.executor == nil {
return fmt.Errorf("command usage:\n%s", tree)
}
return tree.executor(strings.Join(cmdStrs[1:], tree.delimiter))
}
// Pop ourselves off so we can match the sub-commands.
cmdStrs = cmdStrs[1:]
}
for _, subCmd := range tree.subCmds {
if subCmd.trigger == cmdStrs[0] {
// TODO(kate): PoC; splitting/concat on every subcmd is awful.
return subCmd.Execute(strings.Join(cmdStrs, tree.delimiter))
}
}
if tree.executor != nil {
return tree.executor(strings.Join(cmdStrs, tree.delimiter))
}
return fmt.Errorf(`could not match sub-commands "%s"`, cmdStrs[0])
}
// String implements String on the Command interface.
func (tree *command) String() string {
outputBuff := new(bytes.Buffer)
tree.recurseString(outputBuff, 0)
return outputBuff.String()
}
func (tree *command) recurseString(buff io.Writer, indentLevel int) {
if indentLevel > 0 {
fmt.Fprintf(buff, "\n")
for i := 0; i < indentLevel; i++ {
fmt.Fprintf(buff, "\t")
}
}
fmt.Fprintf(buff, tree.trigger)
for _, subCmd := range tree.subCmds {
subCmd.recurseString(buff, indentLevel+1)
}
}