blob: 4ede7e62af821890a384b9344d1cd80cfa7b2add [file] [log] [blame]
package ishell
import (
"bytes"
"fmt"
"sort"
"text/tabwriter"
)
// Cmd is a shell command handler.
type Cmd struct {
// Command name.
Name string
// Command name aliases.
Aliases []string
// Function to execute for the command.
Func func(c *Context)
// One liner help message for the command.
Help string
// More descriptive help message for the command.
LongHelp string
// Completer is custom autocomplete for command.
// It takes in command arguments and returns
// autocomplete options.
// By default all commands get autocomplete of
// subcommands.
// A non-nil Completer overrides the default behaviour.
Completer func(args []string) []string
// subcommands.
children map[string]*Cmd
}
// AddCmd adds cmd as a subcommand.
func (c *Cmd) AddCmd(cmd *Cmd) {
if c.children == nil {
c.children = make(map[string]*Cmd)
}
c.children[cmd.Name] = cmd
}
// DeleteCmd deletes cmd from subcommands.
func (c *Cmd) DeleteCmd(name string) {
delete(c.children, name)
}
// Children returns the subcommands of c.
func (c *Cmd) Children() []*Cmd {
var cmds []*Cmd
for _, cmd := range c.children {
cmds = append(cmds, cmd)
}
sort.Sort(cmdSorter(cmds))
return cmds
}
func (c *Cmd) hasSubcommand() bool {
if len(c.children) > 1 {
return true
}
if _, ok := c.children["help"]; !ok {
return len(c.children) > 0
}
return false
}
// HelpText returns the computed help of the command and its subcommands.
func (c Cmd) HelpText() string {
var b bytes.Buffer
p := func(s ...interface{}) {
fmt.Fprintln(&b)
if len(s) > 0 {
fmt.Fprintln(&b, s...)
}
}
if c.LongHelp != "" {
p(c.LongHelp)
} else if c.Help != "" {
p(c.Help)
} else if c.Name != "" {
p(c.Name, "has no help")
}
if c.hasSubcommand() {
p("Commands:")
w := tabwriter.NewWriter(&b, 0, 4, 2, ' ', 0)
for _, child := range c.Children() {
fmt.Fprintf(w, "\t%s\t\t\t%s\n", child.Name, child.Help)
}
w.Flush()
p()
}
return b.String()
}
// findChildCmd returns the subcommand with matching name or alias.
func (c *Cmd) findChildCmd(name string) *Cmd {
// find perfect matches first
if cmd, ok := c.children[name]; ok {
return cmd
}
// find alias matching the name
for _, cmd := range c.children {
for _, alias := range cmd.Aliases {
if alias == name {
return cmd
}
}
}
return nil
}
// FindCmd finds the matching Cmd for args.
// It returns the Cmd and the remaining args.
func (c Cmd) FindCmd(args []string) (*Cmd, []string) {
var cmd *Cmd
for i, arg := range args {
if cmd1 := c.findChildCmd(arg); cmd1 != nil {
cmd = cmd1
c = *cmd
continue
}
return cmd, args[i:]
}
return cmd, nil
}
type cmdSorter []*Cmd
func (c cmdSorter) Len() int { return len(c) }
func (c cmdSorter) Less(i, j int) bool { return c[i].Name < c[j].Name }
func (c cmdSorter) Swap(i, j int) { c[i], c[j] = c[j], c[i] }