mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			721 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			721 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package cli
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"regexp"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"text/template"
 | 
						|
 | 
						|
	"github.com/armon/go-radix"
 | 
						|
	"github.com/posener/complete"
 | 
						|
)
 | 
						|
 | 
						|
// CLI contains the state necessary to run subcommands and parse the
 | 
						|
// command line arguments.
 | 
						|
//
 | 
						|
// CLI also supports nested subcommands, such as "cli foo bar". To use
 | 
						|
// nested subcommands, the key in the Commands mapping below contains the
 | 
						|
// full subcommand. In this example, it would be "foo bar".
 | 
						|
//
 | 
						|
// If you use a CLI with nested subcommands, some semantics change due to
 | 
						|
// ambiguities:
 | 
						|
//
 | 
						|
//   * We use longest prefix matching to find a matching subcommand. This
 | 
						|
//     means if you register "foo bar" and the user executes "cli foo qux",
 | 
						|
//     the "foo" command will be executed with the arg "qux". It is up to
 | 
						|
//     you to handle these args. One option is to just return the special
 | 
						|
//     help return code `RunResultHelp` to display help and exit.
 | 
						|
//
 | 
						|
//   * The help flag "-h" or "-help" will look at all args to determine
 | 
						|
//     the help function. For example: "otto apps list -h" will show the
 | 
						|
//     help for "apps list" but "otto apps -h" will show it for "apps".
 | 
						|
//     In the normal CLI, only the first subcommand is used.
 | 
						|
//
 | 
						|
//   * The help flag will list any subcommands that a command takes
 | 
						|
//     as well as the command's help itself. If there are no subcommands,
 | 
						|
//     it will note this. If the CLI itself has no subcommands, this entire
 | 
						|
//     section is omitted.
 | 
						|
//
 | 
						|
//   * Any parent commands that don't exist are automatically created as
 | 
						|
//     no-op commands that just show help for other subcommands. For example,
 | 
						|
//     if you only register "foo bar", then "foo" is automatically created.
 | 
						|
//
 | 
						|
type CLI struct {
 | 
						|
	// Args is the list of command-line arguments received excluding
 | 
						|
	// the name of the app. For example, if the command "./cli foo bar"
 | 
						|
	// was invoked, then Args should be []string{"foo", "bar"}.
 | 
						|
	Args []string
 | 
						|
 | 
						|
	// Commands is a mapping of subcommand names to a factory function
 | 
						|
	// for creating that Command implementation. If there is a command
 | 
						|
	// with a blank string "", then it will be used as the default command
 | 
						|
	// if no subcommand is specified.
 | 
						|
	//
 | 
						|
	// If the key has a space in it, this will create a nested subcommand.
 | 
						|
	// For example, if the key is "foo bar", then to access it our CLI
 | 
						|
	// must be accessed with "./cli foo bar". See the docs for CLI for
 | 
						|
	// notes on how this changes some other behavior of the CLI as well.
 | 
						|
	//
 | 
						|
	// The factory should be as cheap as possible, ideally only allocating
 | 
						|
	// a struct. The factory may be called multiple times in the course
 | 
						|
	// of a command execution and certain events such as help require the
 | 
						|
	// instantiation of all commands. Expensive initialization should be
 | 
						|
	// deferred to function calls within the interface implementation.
 | 
						|
	Commands map[string]CommandFactory
 | 
						|
 | 
						|
	// HiddenCommands is a list of commands that are "hidden". Hidden
 | 
						|
	// commands are not given to the help function callback and do not
 | 
						|
	// show up in autocomplete. The values in the slice should be equivalent
 | 
						|
	// to the keys in the command map.
 | 
						|
	HiddenCommands []string
 | 
						|
 | 
						|
	// Name defines the name of the CLI.
 | 
						|
	Name string
 | 
						|
 | 
						|
	// Version of the CLI.
 | 
						|
	Version string
 | 
						|
 | 
						|
	// Autocomplete enables or disables subcommand auto-completion support.
 | 
						|
	// This is enabled by default when NewCLI is called. Otherwise, this
 | 
						|
	// must enabled explicitly.
 | 
						|
	//
 | 
						|
	// Autocomplete requires the "Name" option to be set on CLI. This name
 | 
						|
	// should be set exactly to the binary name that is autocompleted.
 | 
						|
	//
 | 
						|
	// Autocompletion is supported via the github.com/posener/complete
 | 
						|
	// library. This library supports bash, zsh and fish. To add support
 | 
						|
	// for other shells, please see that library.
 | 
						|
	//
 | 
						|
	// AutocompleteInstall and AutocompleteUninstall are the global flag
 | 
						|
	// names for installing and uninstalling the autocompletion handlers
 | 
						|
	// for the user's shell. The flag should omit the hyphen(s) in front of
 | 
						|
	// the value. Both single and double hyphens will automatically be supported
 | 
						|
	// for the flag name. These default to `autocomplete-install` and
 | 
						|
	// `autocomplete-uninstall` respectively.
 | 
						|
	//
 | 
						|
	// AutocompleteNoDefaultFlags is a boolean which controls if the default auto-
 | 
						|
	// complete flags like -help and -version are added to the output.
 | 
						|
	//
 | 
						|
	// AutocompleteGlobalFlags are a mapping of global flags for
 | 
						|
	// autocompletion. The help and version flags are automatically added.
 | 
						|
	Autocomplete               bool
 | 
						|
	AutocompleteInstall        string
 | 
						|
	AutocompleteUninstall      string
 | 
						|
	AutocompleteNoDefaultFlags bool
 | 
						|
	AutocompleteGlobalFlags    complete.Flags
 | 
						|
	autocompleteInstaller      autocompleteInstaller // For tests
 | 
						|
 | 
						|
	// HelpFunc and HelpWriter are used to output help information, if
 | 
						|
	// requested.
 | 
						|
	//
 | 
						|
	// HelpFunc is the function called to generate the generic help
 | 
						|
	// text that is shown if help must be shown for the CLI that doesn't
 | 
						|
	// pertain to a specific command.
 | 
						|
	//
 | 
						|
	// HelpWriter is the Writer where the help text is outputted to. If
 | 
						|
	// not specified, it will default to Stderr.
 | 
						|
	HelpFunc   HelpFunc
 | 
						|
	HelpWriter io.Writer
 | 
						|
 | 
						|
	//---------------------------------------------------------------
 | 
						|
	// Internal fields set automatically
 | 
						|
 | 
						|
	once           sync.Once
 | 
						|
	autocomplete   *complete.Complete
 | 
						|
	commandTree    *radix.Tree
 | 
						|
	commandNested  bool
 | 
						|
	commandHidden  map[string]struct{}
 | 
						|
	subcommand     string
 | 
						|
	subcommandArgs []string
 | 
						|
	topFlags       []string
 | 
						|
 | 
						|
	// These are true when special global flags are set. We can/should
 | 
						|
	// probably use a bitset for this one day.
 | 
						|
	isHelp                  bool
 | 
						|
	isVersion               bool
 | 
						|
	isAutocompleteInstall   bool
 | 
						|
	isAutocompleteUninstall bool
 | 
						|
}
 | 
						|
 | 
						|
// NewClI returns a new CLI instance with sensible defaults.
 | 
						|
func NewCLI(app, version string) *CLI {
 | 
						|
	return &CLI{
 | 
						|
		Name:         app,
 | 
						|
		Version:      version,
 | 
						|
		HelpFunc:     BasicHelpFunc(app),
 | 
						|
		Autocomplete: true,
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// IsHelp returns whether or not the help flag is present within the
 | 
						|
// arguments.
 | 
						|
func (c *CLI) IsHelp() bool {
 | 
						|
	c.once.Do(c.init)
 | 
						|
	return c.isHelp
 | 
						|
}
 | 
						|
 | 
						|
// IsVersion returns whether or not the version flag is present within the
 | 
						|
// arguments.
 | 
						|
func (c *CLI) IsVersion() bool {
 | 
						|
	c.once.Do(c.init)
 | 
						|
	return c.isVersion
 | 
						|
}
 | 
						|
 | 
						|
// Run runs the actual CLI based on the arguments given.
 | 
						|
func (c *CLI) Run() (int, error) {
 | 
						|
	c.once.Do(c.init)
 | 
						|
 | 
						|
	// If this is a autocompletion request, satisfy it. This must be called
 | 
						|
	// first before anything else since its possible to be autocompleting
 | 
						|
	// -help or -version or other flags and we want to show completions
 | 
						|
	// and not actually write the help or version.
 | 
						|
	if c.Autocomplete && c.autocomplete.Complete() {
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Just show the version and exit if instructed.
 | 
						|
	if c.IsVersion() && c.Version != "" {
 | 
						|
		c.HelpWriter.Write([]byte(c.Version + "\n"))
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Just print the help when only '-h' or '--help' is passed.
 | 
						|
	if c.IsHelp() && c.Subcommand() == "" {
 | 
						|
		c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.Subcommand())) + "\n"))
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// If we're attempting to install or uninstall autocomplete then handle
 | 
						|
	if c.Autocomplete {
 | 
						|
		// Autocomplete requires the "Name" to be set so that we know what
 | 
						|
		// command to setup the autocomplete on.
 | 
						|
		if c.Name == "" {
 | 
						|
			return 1, fmt.Errorf(
 | 
						|
				"internal error: CLI.Name must be specified for autocomplete to work")
 | 
						|
		}
 | 
						|
 | 
						|
		// If both install and uninstall flags are specified, then error
 | 
						|
		if c.isAutocompleteInstall && c.isAutocompleteUninstall {
 | 
						|
			return 1, fmt.Errorf(
 | 
						|
				"Either the autocomplete install or uninstall flag may " +
 | 
						|
					"be specified, but not both.")
 | 
						|
		}
 | 
						|
 | 
						|
		// If the install flag is specified, perform the install or uninstall
 | 
						|
		if c.isAutocompleteInstall {
 | 
						|
			if err := c.autocompleteInstaller.Install(c.Name); err != nil {
 | 
						|
				return 1, err
 | 
						|
			}
 | 
						|
 | 
						|
			return 0, nil
 | 
						|
		}
 | 
						|
 | 
						|
		if c.isAutocompleteUninstall {
 | 
						|
			if err := c.autocompleteInstaller.Uninstall(c.Name); err != nil {
 | 
						|
				return 1, err
 | 
						|
			}
 | 
						|
 | 
						|
			return 0, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Attempt to get the factory function for creating the command
 | 
						|
	// implementation. If the command is invalid or blank, it is an error.
 | 
						|
	raw, ok := c.commandTree.Get(c.Subcommand())
 | 
						|
	if !ok {
 | 
						|
		c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.subcommandParent())) + "\n"))
 | 
						|
		return 127, nil
 | 
						|
	}
 | 
						|
 | 
						|
	command, err := raw.(CommandFactory)()
 | 
						|
	if err != nil {
 | 
						|
		return 1, err
 | 
						|
	}
 | 
						|
 | 
						|
	// If we've been instructed to just print the help, then print it
 | 
						|
	if c.IsHelp() {
 | 
						|
		c.commandHelp(command)
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// If there is an invalid flag, then error
 | 
						|
	if len(c.topFlags) > 0 {
 | 
						|
		c.HelpWriter.Write([]byte(
 | 
						|
			"Invalid flags before the subcommand. If these flags are for\n" +
 | 
						|
				"the subcommand, please put them after the subcommand.\n\n"))
 | 
						|
		c.commandHelp(command)
 | 
						|
		return 1, nil
 | 
						|
	}
 | 
						|
 | 
						|
	code := command.Run(c.SubcommandArgs())
 | 
						|
	if code == RunResultHelp {
 | 
						|
		// Requesting help
 | 
						|
		c.commandHelp(command)
 | 
						|
		return 1, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return code, nil
 | 
						|
}
 | 
						|
 | 
						|
// Subcommand returns the subcommand that the CLI would execute. For
 | 
						|
// example, a CLI from "--version version --help" would return a Subcommand
 | 
						|
// of "version"
 | 
						|
func (c *CLI) Subcommand() string {
 | 
						|
	c.once.Do(c.init)
 | 
						|
	return c.subcommand
 | 
						|
}
 | 
						|
 | 
						|
// SubcommandArgs returns the arguments that will be passed to the
 | 
						|
// subcommand.
 | 
						|
func (c *CLI) SubcommandArgs() []string {
 | 
						|
	c.once.Do(c.init)
 | 
						|
	return c.subcommandArgs
 | 
						|
}
 | 
						|
 | 
						|
// subcommandParent returns the parent of this subcommand, if there is one.
 | 
						|
// If there isn't on, "" is returned.
 | 
						|
func (c *CLI) subcommandParent() string {
 | 
						|
	// Get the subcommand, if it is "" alread just return
 | 
						|
	sub := c.Subcommand()
 | 
						|
	if sub == "" {
 | 
						|
		return sub
 | 
						|
	}
 | 
						|
 | 
						|
	// Clear any trailing spaces and find the last space
 | 
						|
	sub = strings.TrimRight(sub, " ")
 | 
						|
	idx := strings.LastIndex(sub, " ")
 | 
						|
 | 
						|
	if idx == -1 {
 | 
						|
		// No space means our parent is root
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	return sub[:idx]
 | 
						|
}
 | 
						|
 | 
						|
func (c *CLI) init() {
 | 
						|
	if c.HelpFunc == nil {
 | 
						|
		c.HelpFunc = BasicHelpFunc("app")
 | 
						|
 | 
						|
		if c.Name != "" {
 | 
						|
			c.HelpFunc = BasicHelpFunc(c.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if c.HelpWriter == nil {
 | 
						|
		c.HelpWriter = os.Stderr
 | 
						|
	}
 | 
						|
 | 
						|
	// Build our hidden commands
 | 
						|
	if len(c.HiddenCommands) > 0 {
 | 
						|
		c.commandHidden = make(map[string]struct{})
 | 
						|
		for _, h := range c.HiddenCommands {
 | 
						|
			c.commandHidden[h] = struct{}{}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Build our command tree
 | 
						|
	c.commandTree = radix.New()
 | 
						|
	c.commandNested = false
 | 
						|
	for k, v := range c.Commands {
 | 
						|
		k = strings.TrimSpace(k)
 | 
						|
		c.commandTree.Insert(k, v)
 | 
						|
		if strings.ContainsRune(k, ' ') {
 | 
						|
			c.commandNested = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Go through the key and fill in any missing parent commands
 | 
						|
	if c.commandNested {
 | 
						|
		var walkFn radix.WalkFn
 | 
						|
		toInsert := make(map[string]struct{})
 | 
						|
		walkFn = func(k string, raw interface{}) bool {
 | 
						|
			idx := strings.LastIndex(k, " ")
 | 
						|
			if idx == -1 {
 | 
						|
				// If there is no space, just ignore top level commands
 | 
						|
				return false
 | 
						|
			}
 | 
						|
 | 
						|
			// Trim up to that space so we can get the expected parent
 | 
						|
			k = k[:idx]
 | 
						|
			if _, ok := c.commandTree.Get(k); ok {
 | 
						|
				// Yay we have the parent!
 | 
						|
				return false
 | 
						|
			}
 | 
						|
 | 
						|
			// We're missing the parent, so let's insert this
 | 
						|
			toInsert[k] = struct{}{}
 | 
						|
 | 
						|
			// Call the walk function recursively so we check this one too
 | 
						|
			return walkFn(k, nil)
 | 
						|
		}
 | 
						|
 | 
						|
		// Walk!
 | 
						|
		c.commandTree.Walk(walkFn)
 | 
						|
 | 
						|
		// Insert any that we're missing
 | 
						|
		for k := range toInsert {
 | 
						|
			var f CommandFactory = func() (Command, error) {
 | 
						|
				return &MockCommand{
 | 
						|
					HelpText:  "This command is accessed by using one of the subcommands below.",
 | 
						|
					RunResult: RunResultHelp,
 | 
						|
				}, nil
 | 
						|
			}
 | 
						|
 | 
						|
			c.commandTree.Insert(k, f)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Setup autocomplete if we have it enabled. We have to do this after
 | 
						|
	// the command tree is setup so we can use the radix tree to easily find
 | 
						|
	// all subcommands.
 | 
						|
	if c.Autocomplete {
 | 
						|
		c.initAutocomplete()
 | 
						|
	}
 | 
						|
 | 
						|
	// Process the args
 | 
						|
	c.processArgs()
 | 
						|
}
 | 
						|
 | 
						|
func (c *CLI) initAutocomplete() {
 | 
						|
	if c.AutocompleteInstall == "" {
 | 
						|
		c.AutocompleteInstall = defaultAutocompleteInstall
 | 
						|
	}
 | 
						|
 | 
						|
	if c.AutocompleteUninstall == "" {
 | 
						|
		c.AutocompleteUninstall = defaultAutocompleteUninstall
 | 
						|
	}
 | 
						|
 | 
						|
	if c.autocompleteInstaller == nil {
 | 
						|
		c.autocompleteInstaller = &realAutocompleteInstaller{}
 | 
						|
	}
 | 
						|
 | 
						|
	// Build the root command
 | 
						|
	cmd := c.initAutocompleteSub("")
 | 
						|
 | 
						|
	// For the root, we add the global flags to the "Flags". This way
 | 
						|
	// they don't show up on every command.
 | 
						|
	if !c.AutocompleteNoDefaultFlags {
 | 
						|
		cmd.Flags = map[string]complete.Predictor{
 | 
						|
			"-" + c.AutocompleteInstall:   complete.PredictNothing,
 | 
						|
			"-" + c.AutocompleteUninstall: complete.PredictNothing,
 | 
						|
			"-help":    complete.PredictNothing,
 | 
						|
			"-version": complete.PredictNothing,
 | 
						|
		}
 | 
						|
	}
 | 
						|
	cmd.GlobalFlags = c.AutocompleteGlobalFlags
 | 
						|
 | 
						|
	c.autocomplete = complete.New(c.Name, cmd)
 | 
						|
}
 | 
						|
 | 
						|
// initAutocompleteSub creates the complete.Command for a subcommand with
 | 
						|
// the given prefix. This will continue recursively for all subcommands.
 | 
						|
// The prefix "" (empty string) can be used for the root command.
 | 
						|
func (c *CLI) initAutocompleteSub(prefix string) complete.Command {
 | 
						|
	var cmd complete.Command
 | 
						|
	walkFn := func(k string, raw interface{}) bool {
 | 
						|
		// Ignore the empty key which can be present for default commands.
 | 
						|
		if k == "" {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
 | 
						|
		// Keep track of the full key so that we can nest further if necessary
 | 
						|
		fullKey := k
 | 
						|
 | 
						|
		if len(prefix) > 0 {
 | 
						|
			// If we have a prefix, trim the prefix + 1 (for the space)
 | 
						|
			// Example: turns "sub one" to "one" with prefix "sub"
 | 
						|
			k = k[len(prefix)+1:]
 | 
						|
		}
 | 
						|
 | 
						|
		if idx := strings.Index(k, " "); idx >= 0 {
 | 
						|
			// If there is a space, we trim up to the space. This turns
 | 
						|
			// "sub sub2 sub3" into "sub". The prefix trim above will
 | 
						|
			// trim our current depth properly.
 | 
						|
			k = k[:idx]
 | 
						|
		}
 | 
						|
 | 
						|
		if _, ok := cmd.Sub[k]; ok {
 | 
						|
			// If we already tracked this subcommand then ignore
 | 
						|
			return false
 | 
						|
		}
 | 
						|
 | 
						|
		// If the command is hidden, don't record it at all
 | 
						|
		if _, ok := c.commandHidden[fullKey]; ok {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
 | 
						|
		if cmd.Sub == nil {
 | 
						|
			cmd.Sub = complete.Commands(make(map[string]complete.Command))
 | 
						|
		}
 | 
						|
		subCmd := c.initAutocompleteSub(fullKey)
 | 
						|
 | 
						|
		// Instantiate the command so that we can check if the command is
 | 
						|
		// a CommandAutocomplete implementation. If there is an error
 | 
						|
		// creating the command, we just ignore it since that will be caught
 | 
						|
		// later.
 | 
						|
		impl, err := raw.(CommandFactory)()
 | 
						|
		if err != nil {
 | 
						|
			impl = nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Check if it implements ComandAutocomplete. If so, setup the autocomplete
 | 
						|
		if c, ok := impl.(CommandAutocomplete); ok {
 | 
						|
			subCmd.Args = c.AutocompleteArgs()
 | 
						|
			subCmd.Flags = c.AutocompleteFlags()
 | 
						|
		}
 | 
						|
 | 
						|
		cmd.Sub[k] = subCmd
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	walkPrefix := prefix
 | 
						|
	if walkPrefix != "" {
 | 
						|
		walkPrefix += " "
 | 
						|
	}
 | 
						|
 | 
						|
	c.commandTree.WalkPrefix(walkPrefix, walkFn)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
func (c *CLI) commandHelp(command Command) {
 | 
						|
	// Get the template to use
 | 
						|
	tpl := strings.TrimSpace(defaultHelpTemplate)
 | 
						|
	if t, ok := command.(CommandHelpTemplate); ok {
 | 
						|
		tpl = t.HelpTemplate()
 | 
						|
	}
 | 
						|
	if !strings.HasSuffix(tpl, "\n") {
 | 
						|
		tpl += "\n"
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse it
 | 
						|
	t, err := template.New("root").Parse(tpl)
 | 
						|
	if err != nil {
 | 
						|
		t = template.Must(template.New("root").Parse(fmt.Sprintf(
 | 
						|
			"Internal error! Failed to parse command help template: %s\n", err)))
 | 
						|
	}
 | 
						|
 | 
						|
	// Template data
 | 
						|
	data := map[string]interface{}{
 | 
						|
		"Name": c.Name,
 | 
						|
		"Help": command.Help(),
 | 
						|
	}
 | 
						|
 | 
						|
	// Build subcommand list if we have it
 | 
						|
	var subcommandsTpl []map[string]interface{}
 | 
						|
	if c.commandNested {
 | 
						|
		// Get the matching keys
 | 
						|
		subcommands := c.helpCommands(c.Subcommand())
 | 
						|
		keys := make([]string, 0, len(subcommands))
 | 
						|
		for k := range subcommands {
 | 
						|
			keys = append(keys, k)
 | 
						|
		}
 | 
						|
 | 
						|
		// Sort the keys
 | 
						|
		sort.Strings(keys)
 | 
						|
 | 
						|
		// Figure out the padding length
 | 
						|
		var longest int
 | 
						|
		for _, k := range keys {
 | 
						|
			if v := len(k); v > longest {
 | 
						|
				longest = v
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Go through and create their structures
 | 
						|
		subcommandsTpl = make([]map[string]interface{}, 0, len(subcommands))
 | 
						|
		for _, k := range keys {
 | 
						|
			// Get the command
 | 
						|
			raw, ok := subcommands[k]
 | 
						|
			if !ok {
 | 
						|
				c.HelpWriter.Write([]byte(fmt.Sprintf(
 | 
						|
					"Error getting subcommand %q", k)))
 | 
						|
			}
 | 
						|
			sub, err := raw()
 | 
						|
			if err != nil {
 | 
						|
				c.HelpWriter.Write([]byte(fmt.Sprintf(
 | 
						|
					"Error instantiating %q: %s", k, err)))
 | 
						|
			}
 | 
						|
 | 
						|
			// Find the last space and make sure we only include that last part
 | 
						|
			name := k
 | 
						|
			if idx := strings.LastIndex(k, " "); idx > -1 {
 | 
						|
				name = name[idx+1:]
 | 
						|
			}
 | 
						|
 | 
						|
			subcommandsTpl = append(subcommandsTpl, map[string]interface{}{
 | 
						|
				"Name":        name,
 | 
						|
				"NameAligned": name + strings.Repeat(" ", longest-len(k)),
 | 
						|
				"Help":        sub.Help(),
 | 
						|
				"Synopsis":    sub.Synopsis(),
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	data["Subcommands"] = subcommandsTpl
 | 
						|
 | 
						|
	// Write
 | 
						|
	err = t.Execute(c.HelpWriter, data)
 | 
						|
	if err == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// An error, just output...
 | 
						|
	c.HelpWriter.Write([]byte(fmt.Sprintf(
 | 
						|
		"Internal error rendering help: %s", err)))
 | 
						|
}
 | 
						|
 | 
						|
// helpCommands returns the subcommands for the HelpFunc argument.
 | 
						|
// This will only contain immediate subcommands.
 | 
						|
func (c *CLI) helpCommands(prefix string) map[string]CommandFactory {
 | 
						|
	// If our prefix isn't empty, make sure it ends in ' '
 | 
						|
	if prefix != "" && prefix[len(prefix)-1] != ' ' {
 | 
						|
		prefix += " "
 | 
						|
	}
 | 
						|
 | 
						|
	// Get all the subkeys of this command
 | 
						|
	var keys []string
 | 
						|
	c.commandTree.WalkPrefix(prefix, func(k string, raw interface{}) bool {
 | 
						|
		// Ignore any sub-sub keys, i.e. "foo bar baz" when we want "foo bar"
 | 
						|
		if !strings.Contains(k[len(prefix):], " ") {
 | 
						|
			keys = append(keys, k)
 | 
						|
		}
 | 
						|
 | 
						|
		return false
 | 
						|
	})
 | 
						|
 | 
						|
	// For each of the keys return that in the map
 | 
						|
	result := make(map[string]CommandFactory, len(keys))
 | 
						|
	for _, k := range keys {
 | 
						|
		raw, ok := c.commandTree.Get(k)
 | 
						|
		if !ok {
 | 
						|
			// We just got it via WalkPrefix above, so we just panic
 | 
						|
			panic("not found: " + k)
 | 
						|
		}
 | 
						|
 | 
						|
		// If this is a hidden command, don't show it
 | 
						|
		if _, ok := c.commandHidden[k]; ok {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		result[k] = raw.(CommandFactory)
 | 
						|
	}
 | 
						|
 | 
						|
	return result
 | 
						|
}
 | 
						|
 | 
						|
func (c *CLI) processArgs() {
 | 
						|
	for i, arg := range c.Args {
 | 
						|
		if arg == "--" {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// Check for help flags.
 | 
						|
		if arg == "-h" || arg == "-help" || arg == "--help" {
 | 
						|
			c.isHelp = true
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Check for autocomplete flags
 | 
						|
		if c.Autocomplete {
 | 
						|
			if arg == "-"+c.AutocompleteInstall || arg == "--"+c.AutocompleteInstall {
 | 
						|
				c.isAutocompleteInstall = true
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if arg == "-"+c.AutocompleteUninstall || arg == "--"+c.AutocompleteUninstall {
 | 
						|
				c.isAutocompleteUninstall = true
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if c.subcommand == "" {
 | 
						|
			// Check for version flags if not in a subcommand.
 | 
						|
			if arg == "-v" || arg == "-version" || arg == "--version" {
 | 
						|
				c.isVersion = true
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if arg != "" && arg[0] == '-' {
 | 
						|
				// Record the arg...
 | 
						|
				c.topFlags = append(c.topFlags, arg)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// If we didn't find a subcommand yet and this is the first non-flag
 | 
						|
		// argument, then this is our subcommand.
 | 
						|
		if c.subcommand == "" && arg != "" && arg[0] != '-' {
 | 
						|
			c.subcommand = arg
 | 
						|
			if c.commandNested {
 | 
						|
				// If the command has a space in it, then it is invalid.
 | 
						|
				// Set a blank command so that it fails.
 | 
						|
				if strings.ContainsRune(arg, ' ') {
 | 
						|
					c.subcommand = ""
 | 
						|
					return
 | 
						|
				}
 | 
						|
 | 
						|
				// Determine the argument we look to to end subcommands.
 | 
						|
				// We look at all arguments until one has a space. This
 | 
						|
				// disallows commands like: ./cli foo "bar baz". An argument
 | 
						|
				// with a space is always an argument.
 | 
						|
				j := 0
 | 
						|
				for k, v := range c.Args[i:] {
 | 
						|
					if strings.ContainsRune(v, ' ') {
 | 
						|
						break
 | 
						|
					}
 | 
						|
 | 
						|
					j = i + k + 1
 | 
						|
				}
 | 
						|
 | 
						|
				// Nested CLI, the subcommand is actually the entire
 | 
						|
				// arg list up to a flag that is still a valid subcommand.
 | 
						|
				searchKey := strings.Join(c.Args[i:j], " ")
 | 
						|
				k, _, ok := c.commandTree.LongestPrefix(searchKey)
 | 
						|
				if ok {
 | 
						|
					// k could be a prefix that doesn't contain the full
 | 
						|
					// command such as "foo" instead of "foobar", so we
 | 
						|
					// need to verify that we have an entire key. To do that,
 | 
						|
					// we look for an ending in a space or an end of string.
 | 
						|
					reVerify := regexp.MustCompile(regexp.QuoteMeta(k) + `( |$)`)
 | 
						|
					if reVerify.MatchString(searchKey) {
 | 
						|
						c.subcommand = k
 | 
						|
						i += strings.Count(k, " ")
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// The remaining args the subcommand arguments
 | 
						|
			c.subcommandArgs = c.Args[i+1:]
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If we never found a subcommand and support a default command, then
 | 
						|
	// switch to using that.
 | 
						|
	if c.subcommand == "" {
 | 
						|
		if _, ok := c.Commands[""]; ok {
 | 
						|
			args := c.topFlags
 | 
						|
			args = append(args, c.subcommandArgs...)
 | 
						|
			c.topFlags = nil
 | 
						|
			c.subcommandArgs = args
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// defaultAutocompleteInstall and defaultAutocompleteUninstall are the
 | 
						|
// default values for the autocomplete install and uninstall flags.
 | 
						|
const defaultAutocompleteInstall = "autocomplete-install"
 | 
						|
const defaultAutocompleteUninstall = "autocomplete-uninstall"
 | 
						|
 | 
						|
const defaultHelpTemplate = `
 | 
						|
{{.Help}}{{if gt (len .Subcommands) 0}}
 | 
						|
 | 
						|
Subcommands:
 | 
						|
{{- range $value := .Subcommands }}
 | 
						|
    {{ $value.NameAligned }}    {{ $value.Synopsis }}{{ end }}
 | 
						|
{{- end }}
 | 
						|
`
 |