bonzai/cmd.go

227 lines
5.8 KiB
Go
Raw Normal View History

2022-02-20 00:22:03 +00:00
// Copyright 2022 Robert S. Muhlestein.
// SPDX-License-Identifier: Apache-2.0
2022-02-17 23:09:19 +00:00
package bonzai
import (
"fmt"
2022-02-17 23:09:19 +00:00
"os"
"github.com/rwxrob/bonzai/comp"
2022-02-18 02:59:27 +00:00
"github.com/rwxrob/bonzai/filter"
2022-02-17 23:09:19 +00:00
)
// Cmd is a struct the easier to use and read when creating
// implementations of the Command interface.
//
// Params
//
// Params require a Method. While Methods may receive any number of
// arguments, Params are a way of helping completion for regular
// parameters. Standard completion will not recursively complete
// multiple params, one param per completion.
type Cmd struct {
2022-02-24 14:02:18 +00:00
Name string `json:"name,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Summary string `json:"summary,omitempty"`
Usage string `json:"usage,omitempty"`
Version string `json:"version,omitempty"`
Copyright string `json:"copyright,omitempty"`
License string `json:"license,omitempty"`
Description string `json:"description,omitempty"`
Site string `json:"site,omitempty"`
Source string `json:"source,omitempty"`
Issues string `json:"issues,omitempty"`
Other map[string]string `json:"issues,omitempty"`
Commands []*Cmd `json:"commands,omitempty"`
Params []string `json:"params,omitempty"`
Hidden []string `json:"hide,omitempty"`
Completer comp.Completer `json:"-"`
Call Method `json:"-"`
Caller *Cmd `json:"-"`
_aliases map[string]*Cmd
}
func (x *Cmd) cacheAliases() {
x._aliases = map[string]*Cmd{}
if x.Commands == nil {
return
}
for _, c := range x.Commands {
if c.Aliases == nil {
continue
}
for _, a := range c.Aliases {
x._aliases[a] = c
}
}
2022-02-17 23:09:19 +00:00
}
2022-02-18 03:06:48 +00:00
// Run is for running a command within a specific runtime (shell) and
// performs completion if completion context is detected. Otherwise, it
// executes the leaf Cmd returned from Seek calling its Method, and then
// Exits. Normally, Run is called from within main() to convert the Cmd
// into an actual executable program and normally it exits the program.
// Exiting can be controlled, however, with ExitOn/ExitOff when testing
// or for other purposes requiring multiple Run calls. Using Call
// instead will also just call the Cmd's Call Method without exiting.
2022-02-17 23:09:19 +00:00
// Note: Only bash runtime ("COMP_LINE") is currently supported, but
// others such a zsh and shell-less REPLs are planned.
func (x *Cmd) Run() {
x.cacheAliases()
2022-02-18 03:06:48 +00:00
// bash completion context
2022-02-17 23:09:19 +00:00
line := os.Getenv("COMP_LINE")
if line != "" {
cmd, args := x.Seek(ArgsFrom(line)[1:])
if cmd.Completer == nil {
list := comp.Standard(cmd, args...)
2022-02-18 03:06:48 +00:00
filter.Println(list)
Exit()
2022-02-17 23:09:19 +00:00
}
filter.Println(cmd.Completer(cmd, args...))
2022-02-17 23:09:19 +00:00
Exit()
}
2022-02-18 03:06:48 +00:00
// seek should never fail to return something, but ...
cmd, args := x.Seek(os.Args[1:])
if cmd == nil {
2022-02-24 23:14:12 +00:00
ExitError(x.UsageError())
2022-02-18 03:06:48 +00:00
}
2022-02-17 23:09:19 +00:00
2022-02-18 03:06:48 +00:00
// default to first Command if no Call defined
if cmd.Call == nil {
if cmd.Commands != nil {
def := cmd.Commands[0]
if def.Call == nil {
ExitError("default command \"%v\" must be callable", def.Name)
}
2022-02-24 12:33:54 +00:00
if err := def.Call(x, args...); err != nil {
2022-02-18 03:06:48 +00:00
ExitError(err)
}
Exit()
2022-02-17 23:09:19 +00:00
}
2022-02-24 23:14:12 +00:00
ExitError(x.UsageError())
2022-02-18 03:06:48 +00:00
}
// delegate
2022-02-24 12:33:54 +00:00
if err := cmd.Call(x, args...); err != nil {
2022-02-18 03:06:48 +00:00
ExitError(err)
}
Exit()
2022-02-17 23:09:19 +00:00
}
2022-02-24 23:14:12 +00:00
func (x *Cmd) UsageError() error {
return fmt.Sprintf("usage: %v %v\n", x.Name, x.Usage)
}
2022-02-17 23:09:19 +00:00
// Add creates a new Cmd and sets the name and aliases and adds to
// Commands returning a reference to the new Cmd. The name must be
// first.
func (x *Cmd) Add(name string, aliases ...string) *Cmd {
c := &Cmd{
Name: name,
Aliases: aliases,
}
x.Commands = append(x.Commands, c)
return c
}
// Cmd looks up a given Command by name or name from Aliases.
2022-02-17 23:09:19 +00:00
func (x *Cmd) Cmd(name string) *Cmd {
if x.Commands == nil {
return nil
}
for _, c := range x.Commands {
if name == c.Name {
return c
}
}
if c, has := x._aliases[name]; has {
return c
}
2022-02-17 23:09:19 +00:00
return nil
}
func (x *Cmd) CmdNames() []string {
list := []string{}
for _, c := range x.Commands {
if c.Name == "" {
continue
}
list = append(list, c.Name)
}
return list
}
// Param returns Param matching name if found, empty string if not.
func (x *Cmd) Param(p string) string {
if x.Params == nil {
return ""
}
for _, c := range x.Params {
if p == c {
return c
}
}
return ""
}
// IsHidden returns true if the specified name is in the list of
// Hidden commands.
func (x *Cmd) IsHidden(name string) bool {
if x.Hidden == nil {
return false
}
for _, h := range x.Hidden {
if h == name {
return true
}
}
return false
}
func (x *Cmd) Seek(args []string) (*Cmd, []string) {
if args == nil || len(args) == 0 || x.Commands == nil || len(x.Commands) == 0 {
return x, args
}
cur := x
n := 0
for ; n < len(args); n++ {
next := cur.Cmd(args[n])
if next == nil {
break
}
2022-02-24 14:02:18 +00:00
next.Caller = cur
2022-02-17 23:09:19 +00:00
cur = next
}
return cur, args[n:]
}
// ---------------------- comp.Command interface ----------------------
// mostly to overcome cyclical imports
// GetName fulfills the comp.Command interface.
func (x *Cmd) GetName() string { return x.Name }
// GetCommands fulfills the comp.Command interface.
func (x *Cmd) GetCommands() []string { return x.CmdNames() }
// GetHidden fulfills the comp.Command interface.
func (x *Cmd) GetHidden() []string { return x.Hidden }
// GetParams fulfills the comp.Command interface.
func (x *Cmd) GetParams() []string { return x.Params }
2022-02-24 14:02:18 +00:00
// GetOther fulfills the comp.Command interface.
func (x *Cmd) GetOther() map[string]string { return x.Other }
2022-02-17 23:09:19 +00:00
// GetCompleter fulfills the Command interface.
func (x *Cmd) GetCompleter() comp.Completer { return x.Completer }
2022-02-24 14:02:18 +00:00
// GetCaller fulfills the comp.Command interface.
func (x *Cmd) GetCaller() comp.Command { return x.Caller }