At a high level, the command
package simply traverses a graph of command.Node
objects. A Node
contains two fields:
-
Node.Processor
contains the logic that should be executed when aNode
is reached. -
Node.Edge
is the logic executed to determine whichNode
should be visited next.
The graph is traversed until there aren't any more nodes to traverse (i.e. Node.Edge.Next()
returns nil
) or until the Node.Processor
logic or Node.Edge
logic returns an error.
While the graph logic may seem convoluted, this strucutre allows for features like command execution caching and command shortcuts. Additionally, simple graphs can still be easily constructed with the SerialNodes
and BranchNode
helper functions.
This example constructs a graph that simply works its way through a set of linear nodes:
func SerialGraph() command.Node {
firstNameArg := commander.Arg[string]("FIRST_NAME", "First name")
lastNameArg := commander.Arg[string]("LAST_NAME", "Last name")
excArg := commander.OptionalArg[int]("EXCITEMENT", "How excited you are", commander.Default(1))
return commander.SerialNodes(
commander.Description("A friendly CLI"),
firstNameArg,
lastNameArg,
excArg,
&commander.ExecutorProcessor{func(o command.Output, d *command.Data) {
o.Stdoutf("Hello, %s %s%s\n", firstNameArg.Get(d), lastNameArg.Get(d), strings.Repeat("!", excArg.Get(d)))
}}
)
}
This graph does different things depending on the first argument.
func BranchingGraph() command.Node {
defaultNode := commander.SerialNodes(
command.ExecutorNode(func(o command.Output, d *command.Data) {
o.Stdoutln("Why didn't you pick a door?")
})
)
return commander.BranchNode(map[string]command.Node{
"one": commander.SerialNodes(
command.ExecutorNode(func(o command.Output, d *command.Data) {
o.Stdoutln("Not quite!")
}),
),
"two": commander.SerialNodes(
command.ExecutorNode(func(o command.Output, d *command.Data) {
o.Stdoutln("You won a new car!")
}),
),
"three": commander.SerialNodes(
command.ExecutorNode(func(o command.Output, d *command.Data) {
o.Stdoutln("Try again!")
}),
),
}, defaultNode)
}
As you can see, these two functions alone can be combined to cover a broad range of CLI use cases. By also exposing the Node
, Processor
, and Edge
types, users are empowered to implement even more advanced CLI-specific graph structures.