301 lines
7.0 KiB
Go
301 lines
7.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Command is a subcommand for a cli.App.
|
|
type Command struct {
|
|
// The name of the command
|
|
Name string
|
|
// A list of aliases for the command
|
|
Aliases []string
|
|
// A short description of the usage of this command
|
|
Usage string
|
|
// Custom text to show on USAGE section of help
|
|
UsageText string
|
|
// A longer explanation of how the command works
|
|
Description string
|
|
// A short description of the arguments of this command
|
|
ArgsUsage string
|
|
// The category the command is part of
|
|
Category string
|
|
// The function to call when checking for bash command completions
|
|
BashComplete BashCompleteFunc
|
|
// An action to execute before any sub-subcommands are run, but after the context is ready
|
|
// If a non-nil error is returned, no sub-subcommands are run
|
|
Before BeforeFunc
|
|
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
// It is run even if Action() panics
|
|
After AfterFunc
|
|
// The function to call when this command is invoked
|
|
Action ActionFunc
|
|
// Execute this function if a usage error occurs.
|
|
OnUsageError OnUsageErrorFunc
|
|
// List of child commands
|
|
Subcommands []*Command
|
|
// List of flags to parse
|
|
Flags []Flag
|
|
// Treat all flags as normal arguments if true
|
|
SkipFlagParsing bool
|
|
// Boolean to hide built-in help command and help flag
|
|
HideHelp bool
|
|
// Boolean to hide built-in help command but keep help flag
|
|
// Ignored if HideHelp is true.
|
|
HideHelpCommand bool
|
|
// Boolean to hide this command from help or completion
|
|
Hidden bool
|
|
// Boolean to enable short-option handling so user can combine several
|
|
// single-character bool arguments into one
|
|
// i.e. foobar -o -v -> foobar -ov
|
|
UseShortOptionHandling bool
|
|
|
|
// Full name of command for help, defaults to full command name, including parent commands.
|
|
HelpName string
|
|
commandNamePath []string
|
|
|
|
// CustomHelpTemplate the text template for the command help topic.
|
|
// cli.go uses text/template to render templates. You can
|
|
// render custom help text by setting this variable.
|
|
CustomHelpTemplate string
|
|
}
|
|
|
|
type Commands []*Command
|
|
|
|
type CommandsByName []*Command
|
|
|
|
func (c CommandsByName) Len() int {
|
|
return len(c)
|
|
}
|
|
|
|
func (c CommandsByName) Less(i, j int) bool {
|
|
return lexicographicLess(c[i].Name, c[j].Name)
|
|
}
|
|
|
|
func (c CommandsByName) Swap(i, j int) {
|
|
c[i], c[j] = c[j], c[i]
|
|
}
|
|
|
|
// FullName returns the full name of the command.
|
|
// For subcommands this ensures that parent commands are part of the command path
|
|
func (c *Command) FullName() string {
|
|
if c.commandNamePath == nil {
|
|
return c.Name
|
|
}
|
|
return strings.Join(c.commandNamePath, " ")
|
|
}
|
|
|
|
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
|
func (c *Command) Run(ctx *Context) (err error) {
|
|
if len(c.Subcommands) > 0 {
|
|
return c.startApp(ctx)
|
|
}
|
|
|
|
if !c.HideHelp && HelpFlag != nil {
|
|
// append help to flags
|
|
c.appendFlag(HelpFlag)
|
|
}
|
|
|
|
if ctx.App.UseShortOptionHandling {
|
|
c.UseShortOptionHandling = true
|
|
}
|
|
|
|
set, err := c.parseFlags(ctx.Args(), ctx.shellComplete)
|
|
|
|
context := NewContext(ctx.App, set, ctx)
|
|
context.Command = c
|
|
if checkCommandCompletions(context, c.Name) {
|
|
return nil
|
|
}
|
|
|
|
if err != nil {
|
|
if c.OnUsageError != nil {
|
|
err = c.OnUsageError(context, err, false)
|
|
context.App.handleExitCoder(context, err)
|
|
return err
|
|
}
|
|
_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
|
_, _ = fmt.Fprintln(context.App.Writer)
|
|
_ = ShowCommandHelp(context, c.Name)
|
|
return err
|
|
}
|
|
|
|
if checkCommandHelp(context, c.Name) {
|
|
return nil
|
|
}
|
|
|
|
cerr := checkRequiredFlags(c.Flags, context)
|
|
if cerr != nil {
|
|
_ = ShowCommandHelp(context, c.Name)
|
|
return cerr
|
|
}
|
|
|
|
if c.After != nil {
|
|
defer func() {
|
|
afterErr := c.After(context)
|
|
if afterErr != nil {
|
|
context.App.handleExitCoder(context, err)
|
|
if err != nil {
|
|
err = newMultiError(err, afterErr)
|
|
} else {
|
|
err = afterErr
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
if c.Before != nil {
|
|
err = c.Before(context)
|
|
if err != nil {
|
|
context.App.handleExitCoder(context, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
if c.Action == nil {
|
|
c.Action = helpSubcommand.Action
|
|
}
|
|
|
|
context.Command = c
|
|
err = c.Action(context)
|
|
|
|
if err != nil {
|
|
context.App.handleExitCoder(context, err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *Command) newFlagSet() (*flag.FlagSet, error) {
|
|
return flagSet(c.Name, c.Flags)
|
|
}
|
|
|
|
func (c *Command) useShortOptionHandling() bool {
|
|
return c.UseShortOptionHandling
|
|
}
|
|
|
|
func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
|
|
set, err := c.newFlagSet()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if c.SkipFlagParsing {
|
|
return set, set.Parse(append([]string{"--"}, args.Tail()...))
|
|
}
|
|
|
|
err = parseIter(set, c, args.Tail(), shellComplete)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = normalizeFlags(c.Flags, set)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return set, nil
|
|
}
|
|
|
|
// Names returns the names including short names and aliases.
|
|
func (c *Command) Names() []string {
|
|
return append([]string{c.Name}, c.Aliases...)
|
|
}
|
|
|
|
// HasName returns true if Command.Name matches given name
|
|
func (c *Command) HasName(name string) bool {
|
|
for _, n := range c.Names() {
|
|
if n == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Command) startApp(ctx *Context) error {
|
|
app := &App{
|
|
Metadata: ctx.App.Metadata,
|
|
Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name),
|
|
}
|
|
|
|
if c.HelpName == "" {
|
|
app.HelpName = c.HelpName
|
|
} else {
|
|
app.HelpName = app.Name
|
|
}
|
|
|
|
app.Usage = c.Usage
|
|
app.Description = c.Description
|
|
app.ArgsUsage = c.ArgsUsage
|
|
|
|
// set CommandNotFound
|
|
app.CommandNotFound = ctx.App.CommandNotFound
|
|
app.CustomAppHelpTemplate = c.CustomHelpTemplate
|
|
|
|
// set the flags and commands
|
|
app.Commands = c.Subcommands
|
|
app.Flags = c.Flags
|
|
app.HideHelp = c.HideHelp
|
|
app.HideHelpCommand = c.HideHelpCommand
|
|
|
|
app.Version = ctx.App.Version
|
|
app.HideVersion = ctx.App.HideVersion
|
|
app.Compiled = ctx.App.Compiled
|
|
app.Writer = ctx.App.Writer
|
|
app.ErrWriter = ctx.App.ErrWriter
|
|
app.ExitErrHandler = ctx.App.ExitErrHandler
|
|
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
|
|
|
|
app.categories = newCommandCategories()
|
|
for _, command := range c.Subcommands {
|
|
app.categories.AddCommand(command.Category, command)
|
|
}
|
|
|
|
sort.Sort(app.categories.(*commandCategories))
|
|
|
|
// bash completion
|
|
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
|
if c.BashComplete != nil {
|
|
app.BashComplete = c.BashComplete
|
|
}
|
|
|
|
// set the actions
|
|
app.Before = c.Before
|
|
app.After = c.After
|
|
if c.Action != nil {
|
|
app.Action = c.Action
|
|
} else {
|
|
app.Action = helpSubcommand.Action
|
|
}
|
|
app.OnUsageError = c.OnUsageError
|
|
|
|
for index, cc := range app.Commands {
|
|
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
|
}
|
|
|
|
return app.RunAsSubcommand(ctx)
|
|
}
|
|
|
|
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
func (c *Command) VisibleFlags() []Flag {
|
|
return visibleFlags(c.Flags)
|
|
}
|
|
|
|
func (c *Command) appendFlag(fl Flag) {
|
|
if !hasFlag(c.Flags, fl) {
|
|
c.Flags = append(c.Flags, fl)
|
|
}
|
|
}
|
|
|
|
func hasCommand(commands []*Command, command *Command) bool {
|
|
for _, existing := range commands {
|
|
if command == existing {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|