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
|
|
|
|
2022-04-01 10:12:53 +00:00
|
|
|
package Z
|
2022-02-17 23:09:19 +00:00
|
|
|
|
|
|
|
import (
|
2022-04-09 19:29:37 +00:00
|
|
|
"bytes"
|
2022-02-25 00:24:48 +00:00
|
|
|
"fmt"
|
2022-03-30 12:08:11 +00:00
|
|
|
"log"
|
2022-02-17 23:09:19 +00:00
|
|
|
"os"
|
2022-04-02 20:38:16 +00:00
|
|
|
"strconv"
|
2022-03-27 08:53:11 +00:00
|
|
|
"strings"
|
2022-04-09 19:29:37 +00:00
|
|
|
"text/template"
|
2022-02-17 23:09:19 +00:00
|
|
|
|
2022-04-08 17:38:25 +00:00
|
|
|
"github.com/rwxrob/bonzai"
|
2022-03-23 08:31:00 +00:00
|
|
|
"github.com/rwxrob/fn/each"
|
2022-03-28 16:32:05 +00:00
|
|
|
"github.com/rwxrob/fn/maps"
|
2022-04-02 20:38:16 +00:00
|
|
|
"github.com/rwxrob/fn/redu"
|
2022-03-27 08:53:11 +00:00
|
|
|
"github.com/rwxrob/structs/qstack"
|
2022-04-10 11:30:53 +00:00
|
|
|
"github.com/rwxrob/to"
|
2022-02-17 23:09:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Cmd struct {
|
2022-04-02 11:40:00 +00:00
|
|
|
|
2022-04-11 08:01:29 +00:00
|
|
|
// main documentation, use Get* for filled template
|
|
|
|
Name string `json:"name,omitempty"` // plain
|
|
|
|
Aliases []string `json:"aliases,omitempty"` // plain
|
|
|
|
Summary string `json:"summary,omitempty"` // template
|
|
|
|
Usage string `json:"usage,omitempty"` // template
|
|
|
|
Version string `json:"version,omitempty"` // template
|
|
|
|
Copyright string `json:"copyright,omitempty"` // template
|
|
|
|
License string `json:"license,omitempty"` // template
|
|
|
|
Description string `json:"description,omitempty"` // template
|
|
|
|
Other []Section `json:"other,omitempty"` // template
|
|
|
|
|
|
|
|
// run-time additions to main documentation (ex: {{ exename }})
|
|
|
|
Dynamic template.FuncMap `json:"-"`
|
|
|
|
|
2022-04-11 09:41:40 +00:00
|
|
|
// administrative URLs
|
|
|
|
Site string `json:"site,omitempty"` // template, https:// assumed
|
2022-04-11 08:01:29 +00:00
|
|
|
Source string `json:"source,omitempty"` // template, usually git url
|
2022-04-11 09:41:40 +00:00
|
|
|
Issues string `json:"issues,omitempty"` // template, https:// assumed
|
2022-04-11 08:01:29 +00:00
|
|
|
|
|
|
|
// descending tree, completable
|
|
|
|
Commands []*Cmd `json:"commands,omitempty"`
|
|
|
|
Params []string `json:"params,omitempty"`
|
|
|
|
Hidden []string `json:"hidden,omitempty"`
|
|
|
|
|
2022-04-12 12:46:18 +00:00
|
|
|
// standard or custom completer, usually of form compfoo.New()
|
|
|
|
Comp bonzai.Completer `json:"-"`
|
2022-03-27 11:12:13 +00:00
|
|
|
|
2022-04-11 08:01:29 +00:00
|
|
|
// where the work happens
|
|
|
|
Caller *Cmd `json:"-"`
|
|
|
|
Call Method `json:"-"`
|
2022-04-10 11:30:53 +00:00
|
|
|
|
2022-04-11 08:01:29 +00:00
|
|
|
// faster than lots of "if" in Call
|
|
|
|
MinArgs int `json:"-"` // minimum number of args required (including parms)
|
2022-04-14 11:51:13 +00:00
|
|
|
MaxArgs int `json:"-"` // maximum number of args required (including parms)
|
|
|
|
NumArgs int `json:"-"` // exact number of args required (including parms)
|
2022-04-15 09:56:28 +00:00
|
|
|
NoArgs bool `json:"-"` // must not have any args
|
2022-04-11 08:01:29 +00:00
|
|
|
MinParm int `json:"-"` // minimum number of params required
|
|
|
|
MaxParm int `json:"-"` // maximum number of params required
|
2022-04-14 12:50:19 +00:00
|
|
|
UseConf bool `json:"-"` // requires Z.Conf be assigned
|
|
|
|
UseVars bool `json:"-"` // requires Z.Var be assigned
|
2022-04-11 08:01:29 +00:00
|
|
|
|
2022-04-09 06:51:43 +00:00
|
|
|
_aliases map[string]*Cmd // see cacheAliases called from Run->Seek->Resolve
|
2022-04-06 11:29:29 +00:00
|
|
|
_sections map[string]string // see cacheSections called from Run
|
|
|
|
}
|
|
|
|
|
|
|
|
// Section contains the Other sections of a command. Composition
|
|
|
|
// notation (without Title and Body) is not only supported but
|
|
|
|
// encouraged for clarity when reading the source for documentation.
|
|
|
|
type Section struct {
|
|
|
|
Title string
|
|
|
|
Body string
|
2022-02-18 03:48:20 +00:00
|
|
|
}
|
|
|
|
|
2022-04-08 17:38:25 +00:00
|
|
|
func (s Section) GetTitle() string { return s.Title }
|
|
|
|
func (s Section) GetBody() string { return s.Body }
|
|
|
|
|
2022-04-02 11:40:00 +00:00
|
|
|
// Names returns the Name and any Aliases grouped such that the Name is
|
|
|
|
// always last.
|
|
|
|
func (x *Cmd) Names() []string {
|
|
|
|
var names []string
|
|
|
|
names = append(names, x.Aliases...)
|
|
|
|
names = append(names, x.Name)
|
|
|
|
return names
|
|
|
|
}
|
|
|
|
|
|
|
|
// UsageNames returns single name, joined Names with bar (|) and wrapped
|
|
|
|
// in parentheses, or empty string if no names.
|
2022-04-02 20:38:16 +00:00
|
|
|
func (x *Cmd) UsageNames() string { return UsageGroup(x.Names(), 1, 1) }
|
|
|
|
|
|
|
|
// UsageParams returns the Params in UsageGroup notation.
|
|
|
|
func (x *Cmd) UsageParams() string {
|
|
|
|
return UsageGroup(x.Params, x.MinParm, x.MaxParm)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UsageCmdNames returns the Names for each of its Commands joined, if
|
|
|
|
// more than one, with usage regex notation.
|
|
|
|
func (x *Cmd) UsageCmdNames() string {
|
|
|
|
var names []string
|
|
|
|
for _, n := range x.Commands {
|
|
|
|
names = append(names, n.UsageNames())
|
|
|
|
}
|
|
|
|
return UsageGroup(names, 1, 1)
|
|
|
|
}
|
2022-04-02 11:40:00 +00:00
|
|
|
|
|
|
|
// Title returns a dynamic field of Name and Summary combined (if
|
2022-04-02 20:38:16 +00:00
|
|
|
// exists). If the Name field of the commands is not defined will return
|
2022-04-11 08:01:29 +00:00
|
|
|
// a "{ERROR}". Fills template for Summary.
|
2022-04-02 11:40:00 +00:00
|
|
|
func (x *Cmd) Title() string {
|
2022-04-02 20:38:16 +00:00
|
|
|
if x.Name == "" {
|
|
|
|
return "{ERROR: Name is empty}"
|
|
|
|
}
|
2022-04-11 08:01:29 +00:00
|
|
|
summary := x.GetSummary()
|
2022-04-02 11:40:00 +00:00
|
|
|
switch {
|
2022-04-11 08:01:29 +00:00
|
|
|
case len(summary) > 0:
|
|
|
|
return x.Name + " - " + summary
|
2022-04-02 11:40:00 +00:00
|
|
|
default:
|
|
|
|
return x.Name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetLegal returns a single line with the combined values of the
|
2022-04-02 11:40:00 +00:00
|
|
|
// Name, Version, Copyright, and License. If Version is empty or nil an
|
2022-04-10 21:05:53 +00:00
|
|
|
// empty string is returned instead. GetLegal() is used by the
|
2022-04-02 11:40:00 +00:00
|
|
|
// version builtin command to aggregate all the version information into
|
|
|
|
// a single output.
|
2022-04-10 21:05:53 +00:00
|
|
|
func (x *Cmd) GetLegal() string {
|
|
|
|
|
|
|
|
copyright := x.GetCopyright()
|
|
|
|
license := x.GetLicense()
|
|
|
|
version := x.GetVersion()
|
|
|
|
|
2022-04-02 11:40:00 +00:00
|
|
|
switch {
|
2022-04-10 21:05:53 +00:00
|
|
|
|
|
|
|
case len(copyright) > 0 && len(license) == 0 && len(version) == 0:
|
|
|
|
return x.Name + " " + copyright
|
|
|
|
|
|
|
|
case len(copyright) > 0 && len(license) > 0 && len(version) > 0:
|
|
|
|
return x.Name + " (" + version + ") " +
|
|
|
|
copyright + "\nLicense " + license
|
|
|
|
|
|
|
|
case len(copyright) > 0 && len(license) > 0:
|
|
|
|
return x.Name + " " + copyright + "\nLicense " + license
|
|
|
|
|
|
|
|
case len(copyright) > 0 && len(version) > 0:
|
|
|
|
return x.Name + " (" + version + ") " + copyright
|
|
|
|
|
|
|
|
case len(copyright) > 0:
|
|
|
|
return x.Name + "\n" + copyright
|
|
|
|
|
2022-04-02 11:40:00 +00:00
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
2022-04-10 21:05:53 +00:00
|
|
|
|
2022-04-02 11:40:00 +00:00
|
|
|
}
|
|
|
|
|
2022-04-06 11:29:29 +00:00
|
|
|
// OtherTitles returns just the ordered titles from Other.
|
|
|
|
func (x *Cmd) OtherTitles() []string { return maps.Keys(x._sections) }
|
|
|
|
|
2022-02-18 03:48:20 +00:00
|
|
|
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-04-06 11:29:29 +00:00
|
|
|
func (x *Cmd) cacheSections() {
|
|
|
|
x._sections = map[string]string{}
|
|
|
|
if len(x.Other) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, s := range x.Other {
|
|
|
|
x._sections[s.Title] = s.Body
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-12 09:52:24 +00:00
|
|
|
// Run method resolves aliases and seeks the leaf Cmd. It then calls the
|
|
|
|
// leaf's first-class Call function passing itself as the first argument
|
|
|
|
// along with any remaining command line arguments. Run returns nothing
|
|
|
|
// because it usually exits the program. Normally, Run is called from
|
|
|
|
// within main() to convert the Cmd into an actual executable program.
|
|
|
|
// Exiting can be controlled, however, by calling ExitOn or ExitOff
|
|
|
|
// (primarily for testing). Use Call instead of Run when delegation is
|
|
|
|
// needed. However, avoid tight-coupling that comes from delegation with
|
2022-04-14 11:51:13 +00:00
|
|
|
// Call when possible. Also, Call automatically assumes the proper
|
|
|
|
// number and type of arguments have already been checked (see MinArgs,
|
|
|
|
// MaxArgs, NumArgs, etc.) which is normally done by Run. Use
|
|
|
|
// a high-level branch pkg instead (which is idiomatic for good Bonzai
|
|
|
|
// branch development).
|
2022-04-12 09:52:24 +00:00
|
|
|
//
|
|
|
|
// Handling Completion
|
|
|
|
//
|
|
|
|
// Since Run is the main execution entry point for all Bonzai command
|
|
|
|
// trees it is also responsible for handling completion (tab or
|
|
|
|
// otherwise). Therefore, all Run methods have two modes: delegation and
|
|
|
|
// completion (both are executions of the Bonzai binary command tree).
|
|
|
|
// Delegation is the default mode. Completion mode is triggered by the
|
|
|
|
// detection of the COMP_LINE environment variable.
|
|
|
|
//
|
|
|
|
// COMP_LINE
|
|
|
|
//
|
|
|
|
// When COMP_LINE is set, Run prints a list of possible completions to
|
2022-04-12 12:46:18 +00:00
|
|
|
// standard output by calling its Comp.Complete function
|
2022-04-12 09:52:24 +00:00
|
|
|
// (default Z.Comp). Each Cmd therefore manages its own completion and
|
|
|
|
// can draw from a rich ecosystem of Completers or assign its own custom
|
|
|
|
// one. This enables very powerful completions including dynamic
|
|
|
|
// completions that query the network or the local execution
|
|
|
|
// environment. Since Go can run on pretty much every device
|
|
|
|
// architecture right now, that's a lot of possibilities. Even
|
|
|
|
// a line-based calculator can be implemented as a Completer. AI
|
|
|
|
// completers are also fully supported by this approach. Intelligent
|
|
|
|
// completion eliminates the need for overly complex and error-prone
|
|
|
|
// (getopt) argument signatures for all Bonzai commands.
|
|
|
|
//
|
|
|
|
// Why COMP_LINE?
|
|
|
|
//
|
|
|
|
// Setting COMP_LINE has been a bash shell standard for more than a few
|
|
|
|
// decades. (Unfortunately, zsh dubiously chose to not support it for no
|
|
|
|
// good reason.) COMP_LINE completion, therefore, is the only planned
|
|
|
|
// method of detecting completion context. Enabling it in bash for any
|
|
|
|
// command becomes a simple matter of "complete -C foo foo" (rather than
|
|
|
|
// forcing users to evaluate thousands of lines of shell code to enable
|
|
|
|
// completion for even minimally complex command trees as other
|
|
|
|
// "commanders" require). Any code will work that sets COMP_LINE before
|
|
|
|
// calling Cmd.Run and receives a list of lines to standard
|
|
|
|
// output with completion candidates.
|
2022-02-17 23:09:19 +00:00
|
|
|
func (x *Cmd) Run() {
|
2022-04-08 07:59:50 +00:00
|
|
|
defer TrapPanic()
|
2022-02-17 23:09:19 +00:00
|
|
|
|
2022-04-14 11:51:13 +00:00
|
|
|
// returns throughout are because Exit* can be disabled
|
|
|
|
// see ExitOff/On
|
|
|
|
|
2022-04-06 11:29:29 +00:00
|
|
|
x.cacheSections()
|
2022-02-18 03:48:20 +00:00
|
|
|
|
2022-04-01 10:12:53 +00:00
|
|
|
// resolve Z.Aliases (if completion didn't replace them)
|
2022-03-30 13:30:41 +00:00
|
|
|
if len(os.Args) > 1 {
|
2022-03-28 16:32:05 +00:00
|
|
|
args := []string{os.Args[0]}
|
|
|
|
alias := Aliases[os.Args[1]]
|
|
|
|
if alias != nil {
|
|
|
|
args = append(args, alias...)
|
|
|
|
args = append(args, os.Args[2:]...)
|
|
|
|
os.Args = args
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 11:51:13 +00:00
|
|
|
// COMPLETION
|
2022-04-09 06:51:43 +00:00
|
|
|
|
2022-04-12 09:52:24 +00:00
|
|
|
line := os.Getenv("COMP_LINE")
|
2022-02-17 23:09:19 +00:00
|
|
|
if line != "" {
|
2022-03-28 16:32:05 +00:00
|
|
|
var list []string
|
2022-04-12 12:46:18 +00:00
|
|
|
|
|
|
|
// find the leaf command
|
2022-03-28 16:32:05 +00:00
|
|
|
lineargs := ArgsFrom(line)
|
2022-03-30 00:08:31 +00:00
|
|
|
if len(lineargs) == 2 {
|
2022-03-28 16:32:05 +00:00
|
|
|
list = append(list, maps.KeysWithPrefix(Aliases, lineargs[1])...)
|
|
|
|
}
|
|
|
|
cmd, args := x.Seek(lineargs[1:])
|
2022-04-12 12:46:18 +00:00
|
|
|
|
|
|
|
// default completer or package aliases, always exits
|
|
|
|
if cmd.Comp == nil {
|
|
|
|
if Comp != nil {
|
|
|
|
list = append(list, Comp.Complete(cmd, args...)...)
|
|
|
|
}
|
2022-03-30 11:38:14 +00:00
|
|
|
if len(list) == 1 && len(lineargs) == 2 {
|
2022-03-30 00:08:31 +00:00
|
|
|
if v, has := Aliases[list[0]]; has {
|
2022-03-30 15:15:29 +00:00
|
|
|
fmt.Println(strings.Join(EscAll(v), " "))
|
2022-03-30 00:08:31 +00:00
|
|
|
Exit()
|
2022-04-14 11:51:13 +00:00
|
|
|
return
|
2022-03-30 00:08:31 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-27 07:02:28 +00:00
|
|
|
each.Println(list)
|
2022-02-18 03:06:48 +00:00
|
|
|
Exit()
|
2022-04-14 11:51:13 +00:00
|
|
|
return
|
2022-02-17 23:09:19 +00:00
|
|
|
}
|
2022-04-12 12:46:18 +00:00
|
|
|
|
|
|
|
// own completer, delegate
|
|
|
|
each.Println(cmd.Comp.Complete(cmd, args...))
|
2022-02-17 23:09:19 +00:00
|
|
|
Exit()
|
2022-04-14 11:51:13 +00:00
|
|
|
return
|
2022-02-17 23:09:19 +00:00
|
|
|
}
|
|
|
|
|
2022-04-14 11:51:13 +00:00
|
|
|
// DELEGATION
|
2022-04-12 09:52:24 +00:00
|
|
|
|
2022-02-18 03:06:48 +00:00
|
|
|
// seek should never fail to return something, but ...
|
|
|
|
cmd, args := x.Seek(os.Args[1:])
|
2022-04-09 06:51:43 +00:00
|
|
|
|
2022-02-18 03:06:48 +00:00
|
|
|
if cmd == nil {
|
2022-04-14 11:51:13 +00:00
|
|
|
ExitError(IncorrectUsage{cmd})
|
|
|
|
return
|
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 {
|
2022-03-26 21:12:50 +00:00
|
|
|
if len(cmd.Commands) > 0 {
|
2022-04-08 10:46:02 +00:00
|
|
|
fcmd := cmd.Commands[0]
|
|
|
|
if fcmd.Call == nil {
|
2022-04-14 11:39:43 +00:00
|
|
|
ExitError(DefCmdReqCall{cmd})
|
|
|
|
return
|
2022-04-06 13:05:10 +00:00
|
|
|
}
|
2022-04-08 10:46:02 +00:00
|
|
|
fcmd.Caller = cmd
|
|
|
|
cmd = fcmd
|
2022-03-26 21:12:50 +00:00
|
|
|
} else {
|
2022-04-14 11:39:43 +00:00
|
|
|
ExitError(NoCallNoCommands{cmd})
|
|
|
|
return
|
2022-02-17 23:09:19 +00:00
|
|
|
}
|
2022-02-18 03:06:48 +00:00
|
|
|
}
|
|
|
|
|
2022-04-14 11:39:43 +00:00
|
|
|
switch {
|
2022-04-15 09:56:28 +00:00
|
|
|
case len(args) > 0 && cmd.NoArgs:
|
|
|
|
ExitError(TooManyArgs{cmd})
|
|
|
|
return
|
2022-04-14 11:39:43 +00:00
|
|
|
case len(args) < cmd.MinArgs:
|
|
|
|
ExitError(NotEnoughArgs{cmd})
|
2022-04-14 12:50:19 +00:00
|
|
|
return
|
2022-04-14 11:39:43 +00:00
|
|
|
case cmd.MaxArgs > 0 && len(args) > cmd.MaxArgs:
|
|
|
|
ExitError(TooManyArgs{cmd})
|
2022-04-14 12:50:19 +00:00
|
|
|
return
|
2022-04-14 11:39:43 +00:00
|
|
|
case cmd.NumArgs > 0 && len(args) != cmd.NumArgs:
|
|
|
|
ExitError(WrongNumArgs{cmd})
|
2022-04-14 12:50:19 +00:00
|
|
|
return
|
|
|
|
case cmd.UseConf && Conf == nil:
|
|
|
|
ExitError(UsesConf{cmd})
|
|
|
|
return
|
|
|
|
case cmd.UseVars && Vars == nil:
|
|
|
|
ExitError(UsesVars{cmd})
|
|
|
|
return
|
2022-04-09 20:27:24 +00:00
|
|
|
}
|
|
|
|
|
2022-02-18 03:06:48 +00:00
|
|
|
// delegate
|
2022-04-06 13:05:10 +00:00
|
|
|
if cmd.Caller == nil {
|
|
|
|
cmd.Caller = x
|
|
|
|
}
|
2022-03-26 21:12:50 +00:00
|
|
|
if err := cmd.Call(cmd, args...); err != nil {
|
2022-02-18 03:06:48 +00:00
|
|
|
ExitError(err)
|
2022-04-14 11:51:13 +00:00
|
|
|
return
|
2022-02-18 03:06:48 +00:00
|
|
|
}
|
|
|
|
Exit()
|
2022-02-17 23:09:19 +00:00
|
|
|
}
|
|
|
|
|
2022-04-11 18:08:31 +00:00
|
|
|
// Root returns the root Cmd from the current Path. This must always be
|
|
|
|
// calculated every time since any Cmd can change positions and
|
|
|
|
// pedigrees at any time at run time. Returns self if no PathCmds found.
|
|
|
|
func (x *Cmd) Root() *Cmd {
|
|
|
|
cmds := x.PathCmds()
|
|
|
|
if len(cmds) > 0 {
|
|
|
|
return cmds[0].Caller
|
|
|
|
}
|
|
|
|
return x.Caller
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-04-09 06:51:43 +00:00
|
|
|
// Resolve looks up a given Command by name or alias from Aliases
|
|
|
|
// (caching a lookup map of aliases in the process).
|
2022-04-02 20:38:16 +00:00
|
|
|
func (x *Cmd) Resolve(name string) *Cmd {
|
2022-04-09 06:51:43 +00:00
|
|
|
|
2022-02-17 23:09:19 +00:00
|
|
|
if x.Commands == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-04-09 06:51:43 +00:00
|
|
|
|
2022-02-17 23:09:19 +00:00
|
|
|
for _, c := range x.Commands {
|
|
|
|
if name == c.Name {
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
}
|
2022-04-09 06:51:43 +00:00
|
|
|
|
|
|
|
if x._aliases == nil {
|
|
|
|
x.cacheAliases()
|
|
|
|
}
|
|
|
|
|
2022-02-18 03:48:20 +00:00
|
|
|
if c, has := x._aliases[name]; has {
|
|
|
|
return c
|
|
|
|
}
|
2022-02-17 23:09:19 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-02 20:38:16 +00:00
|
|
|
// CmdNames returns the names of every Command.
|
2022-02-17 23:09:19 +00:00
|
|
|
func (x *Cmd) CmdNames() []string {
|
|
|
|
list := []string{}
|
|
|
|
for _, c := range x.Commands {
|
|
|
|
if c.Name == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
list = append(list, c.Name)
|
|
|
|
}
|
|
|
|
return list
|
|
|
|
}
|
|
|
|
|
2022-04-02 20:38:16 +00:00
|
|
|
// UsageCmdTitles returns a single string with the titles of each
|
|
|
|
// subcommand indented and with a maximum title signature length for
|
|
|
|
// justification. Hidden commands are not included. Note that the order
|
|
|
|
// of the Commands is preserved (not necessarily alphabetic).
|
|
|
|
func (x *Cmd) UsageCmdTitles() string {
|
|
|
|
var set []string
|
|
|
|
var summaries []string
|
|
|
|
for _, c := range x.Commands {
|
|
|
|
set = append(set, strings.Join(c.Names(), "|"))
|
2022-04-10 21:05:53 +00:00
|
|
|
summaries = append(summaries, c.GetSummary())
|
2022-04-02 20:38:16 +00:00
|
|
|
}
|
|
|
|
longest := redu.Longest(set)
|
|
|
|
var buf string
|
|
|
|
for n := 0; n < len(set); n++ {
|
|
|
|
if len(summaries[n]) > 0 {
|
|
|
|
buf += fmt.Sprintf(`%-`+strconv.Itoa(longest)+"v - %v\n", set[n], summaries[n])
|
|
|
|
} else {
|
|
|
|
buf += fmt.Sprintf(`%-`+strconv.Itoa(longest)+"v\n", set[n])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return buf
|
|
|
|
}
|
|
|
|
|
2022-02-17 23:09:19 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// Seek checks the args for command names returning the deepest along
|
|
|
|
// with the remaining arguments. Typically the args passed are directly
|
|
|
|
// from the command line.
|
2022-02-17 23:09:19 +00:00
|
|
|
func (x *Cmd) Seek(args []string) (*Cmd, []string) {
|
2022-02-25 07:37:29 +00:00
|
|
|
if args == nil || x.Commands == nil {
|
2022-02-17 23:09:19 +00:00
|
|
|
return x, args
|
|
|
|
}
|
|
|
|
cur := x
|
|
|
|
n := 0
|
|
|
|
for ; n < len(args); n++ {
|
2022-04-02 20:38:16 +00:00
|
|
|
next := cur.Resolve(args[n])
|
2022-02-17 23:09:19 +00:00
|
|
|
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:]
|
|
|
|
}
|
|
|
|
|
2022-04-11 18:08:31 +00:00
|
|
|
// PathCmds returns the path of commands used to arrive at this
|
|
|
|
// command. The path is determined by walking backward from current
|
|
|
|
// Caller up rather than depending on anything from the command line
|
|
|
|
// used to invoke the composing binary. Also see Path, PathNames.
|
|
|
|
func (x *Cmd) PathCmds() []*Cmd {
|
|
|
|
path := qstack.New[*Cmd]()
|
|
|
|
path.Unshift(x)
|
|
|
|
for p := x.Caller; p != nil; p = p.Caller {
|
|
|
|
path.Unshift(p)
|
|
|
|
}
|
|
|
|
path.Shift()
|
|
|
|
return path.Items()
|
|
|
|
}
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// PathNames returns the path of command names used to arrive at this
|
2022-04-08 17:38:25 +00:00
|
|
|
// command. The path is determined by walking backward from current
|
|
|
|
// Caller up rather than depending on anything from the command line
|
2022-04-10 21:05:53 +00:00
|
|
|
// used to invoke the composing binary. Also see Path.
|
|
|
|
func (x *Cmd) PathNames() []string {
|
2022-04-08 17:38:25 +00:00
|
|
|
path := qstack.New[string]()
|
|
|
|
path.Unshift(x.Name)
|
2022-03-27 08:53:11 +00:00
|
|
|
for p := x.Caller; p != nil; p = p.Caller {
|
2022-04-08 17:38:25 +00:00
|
|
|
path.Unshift(p.Name)
|
2022-03-27 08:53:11 +00:00
|
|
|
}
|
2022-04-08 17:38:25 +00:00
|
|
|
path.Shift()
|
|
|
|
return path.Items()
|
2022-03-27 08:53:11 +00:00
|
|
|
}
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// Path returns a dotted notation of the PathNames including an initial
|
2022-04-09 08:46:39 +00:00
|
|
|
// dot (for root). This is compatible yq query expressions and useful
|
|
|
|
// for associating configuration and other data specifically with this
|
2022-04-14 13:10:21 +00:00
|
|
|
// command. If any arguments are passed then will be added with dots
|
|
|
|
// between them.
|
|
|
|
func (x *Cmd) Path(more ...string) string {
|
|
|
|
if len(more) > 0 {
|
|
|
|
list := x.PathNames()
|
|
|
|
list = append(list, more...)
|
|
|
|
return "." + strings.Join(list, ".")
|
|
|
|
}
|
2022-04-10 21:05:53 +00:00
|
|
|
return "." + strings.Join(x.PathNames(), ".")
|
2022-03-27 12:25:17 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 12:08:11 +00:00
|
|
|
// Log is currently short for log.Printf() but may be supplemented in
|
|
|
|
// the future to have more fine-grained control of logging.
|
|
|
|
func (x *Cmd) Log(format string, a ...any) {
|
|
|
|
log.Printf(format, a...)
|
|
|
|
}
|
|
|
|
|
2022-04-15 16:43:52 +00:00
|
|
|
// C is a shorter version of
|
|
|
|
// strings.TrimSpace(Z.Conf.Query(x.Path()+"."+q)) for convenience. The
|
|
|
|
// yq YAML/JSON queries unreliably add line returns sometimes and other
|
|
|
|
// times not. If a line return is wanted, the caller will need to use
|
|
|
|
// fmt.Println. Also see UseConf.
|
2022-04-14 13:25:08 +00:00
|
|
|
func (x *Cmd) C(q string) (string, error) {
|
2022-04-08 17:38:25 +00:00
|
|
|
if Conf == nil {
|
2022-04-14 13:25:08 +00:00
|
|
|
return "", UsesConf{x}
|
2022-04-08 17:38:25 +00:00
|
|
|
}
|
2022-04-11 11:47:36 +00:00
|
|
|
path := x.Path()
|
|
|
|
if path != "." {
|
|
|
|
path += "."
|
|
|
|
}
|
2022-04-15 16:43:52 +00:00
|
|
|
res, err := Conf.Query(path + q)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(res), nil
|
2022-04-11 11:47:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get is a shorter version of Z.Vars.Get(x.Path()+"."+key) for
|
2022-04-15 16:43:52 +00:00
|
|
|
// convenience. Also see UseVars.
|
|
|
|
func (x *Cmd) Get(key string) (string, error) {
|
2022-04-11 11:47:36 +00:00
|
|
|
if Vars == nil {
|
2022-04-15 16:43:52 +00:00
|
|
|
return "", UsesVars{x}
|
2022-04-11 11:47:36 +00:00
|
|
|
}
|
|
|
|
path := x.Path()
|
|
|
|
if path != "." {
|
|
|
|
path += "."
|
|
|
|
}
|
2022-04-15 16:43:52 +00:00
|
|
|
return Vars.Get(path + key), nil
|
2022-04-11 11:47:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set is a shorter version of Z.Vars.Set(x.Path()+"."+key.val) for
|
2022-04-14 12:50:19 +00:00
|
|
|
// convenience. Logs the error Z.Vars is not defined (see UseVars).
|
2022-04-11 11:47:36 +00:00
|
|
|
func (x *Cmd) Set(key, val string) error {
|
|
|
|
if Vars == nil {
|
2022-04-14 12:50:19 +00:00
|
|
|
return UsesVars{x}
|
2022-04-11 11:47:36 +00:00
|
|
|
}
|
|
|
|
path := x.Path()
|
|
|
|
if path != "." {
|
|
|
|
path += "."
|
|
|
|
}
|
|
|
|
return Vars.Set(path+key, val)
|
2022-04-08 17:38:25 +00:00
|
|
|
}
|
2022-02-17 23:09:19 +00:00
|
|
|
|
2022-04-09 19:29:37 +00:00
|
|
|
// Fill fills out the passed text/template string using the Cmd instance
|
|
|
|
// as the data object source for the template. It is called by the Get*
|
2022-04-10 21:05:53 +00:00
|
|
|
// family of field accessors but can be called directly as well. Also
|
|
|
|
// see markfunc.go for list of predefined template functions.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) Fill(tmpl string) string {
|
2022-04-10 11:30:53 +00:00
|
|
|
funcs := to.MergedMaps(markFuncMap, x.Dynamic)
|
|
|
|
t, err := template.New("t").Funcs(funcs).Parse(tmpl)
|
2022-04-09 19:29:37 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := t.Execute(&buf, x); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2022-04-14 14:33:47 +00:00
|
|
|
// UsageError returns IncorrectUsage for self.
|
|
|
|
func (x *Cmd) UsageError() error {
|
|
|
|
return IncorrectUsage{x}
|
|
|
|
}
|
|
|
|
|
2022-04-08 17:38:25 +00:00
|
|
|
// --------------------- bonzai.Command interface ---------------------
|
2022-02-17 23:09:19 +00:00
|
|
|
|
2022-04-11 08:01:29 +00:00
|
|
|
// GetName fulfills the bonzai.Command interface. No Fill.
|
|
|
|
func (x *Cmd) GetName() string { return x.Name }
|
2022-02-17 23:09:19 +00:00
|
|
|
|
2022-04-11 08:01:29 +00:00
|
|
|
// GetTitle fulfills the bonzai.Command interface. No Fill.
|
|
|
|
func (x *Cmd) GetTitle() string { return x.Title() }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetAliases fulfills the bonzai.Command interface. No Fill.
|
2022-04-08 17:38:25 +00:00
|
|
|
func (x *Cmd) GetAliases() []string { return x.Aliases }
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetSummary fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetSummary() string { return x.Fill(x.Summary) }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-15 09:56:28 +00:00
|
|
|
// GetUsage fulfills the bonzai.Command interface. Uses x.Usage if not
|
|
|
|
// empty. Otherwise, calls UsageFunc to get usage, then uses Fill.
|
|
|
|
func (x *Cmd) GetUsage() string {
|
|
|
|
usage := x.Usage
|
|
|
|
if usage == "" {
|
|
|
|
usage = UsageFunc(x)
|
|
|
|
}
|
|
|
|
return x.Fill(usage)
|
|
|
|
}
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetVersion fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetVersion() string { return x.Fill(x.Version) }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetCopyright fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetCopyright() string { return x.Fill(x.Copyright) }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetLicense fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetLicense() string { return x.Fill(x.License) }
|
2022-02-17 23:09:19 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetDescription fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetDescription() string { return x.Fill(x.Description) }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetSite fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetSite() string { return x.Fill(x.Site) }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetSource fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetSource() string { return x.Fill(x.Source) }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetIssues fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetIssues() string { return x.Fill(x.Issues) }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetMinArgs fulfills the bonzai.Command interface. No Fill.
|
2022-04-08 17:38:25 +00:00
|
|
|
func (x *Cmd) GetMinArgs() int { return x.MinArgs }
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetMinParm fulfills the bonzai.Command interface. No Fill.
|
2022-04-08 17:38:25 +00:00
|
|
|
func (x *Cmd) GetMinParm() int { return x.MinParm }
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetMaxParm fulfills the bonzai.Command interface. No Fill.
|
2022-04-08 17:38:25 +00:00
|
|
|
func (x *Cmd) GetMaxParm() int { return x.MaxParm }
|
|
|
|
|
2022-04-14 12:50:19 +00:00
|
|
|
// GetUseConf fulfills the bonzai.Command interface. No Fill.
|
|
|
|
func (x *Cmd) GetUseConf() bool { return x.UseConf }
|
2022-04-08 17:38:25 +00:00
|
|
|
|
2022-04-14 12:50:19 +00:00
|
|
|
// GetUseVars fulfills the bonzai.Command interface. No Fill.
|
|
|
|
func (x *Cmd) GetUseVars() bool { return x.UseVars }
|
2022-04-09 20:27:24 +00:00
|
|
|
|
2022-04-08 17:38:25 +00:00
|
|
|
// GetCommands fulfills the bonzai.Command interface.
|
|
|
|
func (x *Cmd) GetCommands() []bonzai.Command {
|
|
|
|
var commands []bonzai.Command
|
|
|
|
for _, s := range x.Commands {
|
|
|
|
commands = append(commands, bonzai.Command(s))
|
|
|
|
}
|
|
|
|
return commands
|
|
|
|
}
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetCommandNames fulfills the bonzai.Command interface. No Fill.
|
2022-04-08 17:38:25 +00:00
|
|
|
func (x *Cmd) GetCommandNames() []string { return x.CmdNames() }
|
|
|
|
|
|
|
|
// GetHidden fulfills the bonzai.Command interface.
|
2022-02-17 23:09:19 +00:00
|
|
|
func (x *Cmd) GetHidden() []string { return x.Hidden }
|
|
|
|
|
2022-04-08 17:38:25 +00:00
|
|
|
// GetParams fulfills the bonzai.Command interface.
|
2022-02-17 23:09:19 +00:00
|
|
|
func (x *Cmd) GetParams() []string { return x.Params }
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetOther fulfills the bonzai.Command interface. Uses Fill.
|
2022-04-08 17:38:25 +00:00
|
|
|
func (x *Cmd) GetOther() []bonzai.Section {
|
|
|
|
var sections []bonzai.Section
|
|
|
|
for _, s := range x.Other {
|
2022-04-09 19:29:37 +00:00
|
|
|
s.Body = x.Fill(s.Body)
|
2022-04-08 17:38:25 +00:00
|
|
|
sections = append(sections, bonzai.Section(s))
|
|
|
|
}
|
|
|
|
return sections
|
|
|
|
}
|
|
|
|
|
2022-04-10 21:05:53 +00:00
|
|
|
// GetOtherTitles fulfills the bonzai.Command interface. No Fill.
|
2022-04-09 19:29:37 +00:00
|
|
|
func (x *Cmd) GetOtherTitles() []string {
|
|
|
|
var titles []string
|
|
|
|
for _, title := range x.OtherTitles() {
|
2022-04-10 21:05:53 +00:00
|
|
|
titles = append(titles, title)
|
2022-04-09 19:29:37 +00:00
|
|
|
}
|
|
|
|
return titles
|
|
|
|
}
|
2022-02-24 14:02:18 +00:00
|
|
|
|
2022-04-12 12:46:18 +00:00
|
|
|
// GetComp fulfills the Command interface.
|
|
|
|
func (x *Cmd) GetComp() bonzai.Completer { return x.Comp }
|
2022-02-24 14:02:18 +00:00
|
|
|
|
2022-04-08 17:38:25 +00:00
|
|
|
// GetCaller fulfills the bonzai.Command interface.
|
|
|
|
func (x *Cmd) GetCaller() bonzai.Command { return x.Caller }
|