urfave-cli/command.go

380 lines
9.7 KiB
Go
Raw Normal View History

package cli
import (
"flag"
"fmt"
"sort"
"strings"
)
2013-11-01 14:31:37 +00:00
// Command is a subcommand for a cli.App.
type Command struct {
2013-11-01 14:31:37 +00:00
// The name of the command
Name string
// short name of the command. Typically one character (deprecated, use `Aliases`)
2013-11-01 14:31:37 +00:00
ShortName string
// A list of aliases for the command
Aliases []string
2013-11-01 14:31:37 +00:00
// 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
2013-09-24 01:41:31 +00:00
Description string
2015-08-03 23:51:11 +00:00
// A short description of the arguments of this command
ArgsUsage string
2014-12-15 22:35:49 +00:00
// The category the command is part of
Category string
2014-04-10 17:14:13 +00:00
// 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
2014-11-18 22:44:21 +00:00
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
2013-11-01 14:31:37 +00:00
// The function to call when this command is invoked
Action interface{}
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
// of deprecation period has passed, maybe?
// Execute this function if a usage error occurs.
OnUsageError OnUsageErrorFunc
// List of child commands
2014-12-15 22:35:49 +00:00
Subcommands Commands
// List of flags to parse
Flags []Flag
2014-04-14 21:44:32 +00:00
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Skip argument reordering which attempts to move flags before arguments,
// but only works if all flags appear after all arguments. This behavior was
// removed n version 2 since it only works under specific conditions so we
// backport here by exposing it as an option for compatibility.
SkipArgReorder bool
2014-07-13 13:16:30 +00:00
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide this command from help or completion
Hidden bool
// Boolean to enable short-option handling so user can combine several
2018-10-19 00:56:13 +00:00
// single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool
2015-08-13 05:14:26 +00:00
// Full name of command for help, defaults to full command name, including parent commands.
2015-08-13 04:43:14 +00:00
HelpName string
commandNamePath []string
2016-11-25 08:16:48 +00:00
// 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 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, " ")
}
2013-07-19 02:23:00 +00:00
// Commands is a slice of Command
2014-12-15 22:35:49 +00:00
type Commands []Command
// 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 != BoolFlag{}) {
2014-07-13 13:16:30 +00:00
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
}
2013-08-14 04:40:39 +00:00
2019-08-05 10:16:30 +00:00
if ctx.App.UseShortOptionHandling {
c.UseShortOptionHandling = true
}
set, err := c.parseFlags(ctx.Args().Tail())
context := NewContext(ctx.App, set, ctx)
context.Command = c
2014-04-10 17:14:13 +00:00
if checkCommandCompletions(context, c.Name) {
return nil
2014-04-10 17:14:13 +00:00
}
2016-11-04 20:56:28 +00:00
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(context, err, false)
2017-06-28 16:52:12 +00:00
context.App.handleExitCoder(context, err)
2016-11-04 20:56:28 +00:00
return err
}
_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
_, _ = fmt.Fprintln(context.App.Writer)
_ = ShowCommandHelp(context, c.Name)
2016-11-04 20:56:28 +00:00
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 {
2017-06-28 16:52:12 +00:00
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 {
_ = ShowCommandHelp(context, c.Name)
2017-06-28 16:52:12 +00:00
context.App.handleExitCoder(context, err)
return err
}
}
if c.Action == nil {
c.Action = helpSubcommand.Action
}
err = HandleAction(c.Action, context)
if err != nil {
2017-06-28 16:52:12 +00:00
context.App.handleExitCoder(context, err)
}
return err
2013-07-19 02:23:00 +00:00
}
2013-07-19 02:30:18 +00:00
func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
if c.SkipFlagParsing {
2019-08-05 10:16:30 +00:00
set, err := c.newFlagSet()
if err != nil {
return nil, err
}
return set, set.Parse(append([]string{"--"}, args...))
}
if !c.SkipArgReorder {
2019-08-25 21:12:13 +00:00
args = reorderArgs(c.Flags, args)
}
set, err := c.newFlagSet()
if err != nil {
return nil, err
}
err = parseIter(set, c, args)
if err != nil {
return nil, err
}
err = normalizeFlags(c.Flags, set)
if err != nil {
return nil, err
}
return set, nil
}
2019-08-05 10:16:30 +00:00
func (c *Command) newFlagSet() (*flag.FlagSet, error) {
return flagSet(c.Name, c.Flags)
}
func (c *Command) useShortOptionHandling() bool {
return c.UseShortOptionHandling
}
2019-08-26 07:18:36 +00:00
// reorderArgs moves all flags (via reorderedArgs) before the rest of
// the arguments (remainingArgs) as this is what flag expects.
2019-08-25 21:12:13 +00:00
func reorderArgs(commandFlags []Flag, args []string) []string {
var remainingArgs, reorderedArgs []string
2019-09-12 06:05:26 +00:00
nextIndexMayContainValue := false
for i, arg := range args {
2019-08-26 07:18:36 +00:00
// dont reorder any args after a --
// read about -- here:
// https://unix.stackexchange.com/questions/11376/what-does-double-dash-mean-also-known-as-bare-double-dash
if arg == "--" {
2019-08-25 21:12:13 +00:00
remainingArgs = append(remainingArgs, args[i:]...)
break
2019-09-12 06:05:26 +00:00
// checks if this arg is a value that should be re-ordered next to its associated flag
} else if nextIndexMayContainValue && !strings.HasPrefix(arg, "-") {
nextIndexMayContainValue = false
2019-08-26 07:18:36 +00:00
reorderedArgs = append(reorderedArgs, arg)
2019-09-12 06:05:26 +00:00
// checks if this is an arg that should be re-ordered
2019-10-02 03:13:04 +00:00
} else if argIsFlag(commandFlags, arg) {
2019-09-12 06:05:26 +00:00
// we have determined that this is a flag that we should re-order
2019-08-26 07:18:36 +00:00
reorderedArgs = append(reorderedArgs, arg)
2019-09-12 06:05:26 +00:00
// if this arg does not contain a "=", then the next index may contain the value for this flag
nextIndexMayContainValue = !strings.Contains(arg, "=")
2019-09-12 06:05:26 +00:00
// simply append any remaining args
} else {
remainingArgs = append(remainingArgs, arg)
}
}
2019-08-26 07:18:36 +00:00
return append(reorderedArgs, remainingArgs...)
}
2014-04-10 17:14:13 +00:00
2019-09-12 06:05:26 +00:00
// argIsFlag checks if an arg is one of our command flags
2019-09-12 02:25:51 +00:00
func argIsFlag(commandFlags []Flag, arg string) bool {
2019-10-02 03:13:04 +00:00
// checks if this is just a `-`, and so definitely not a flag
if arg == "-" {
return false
}
2019-10-02 03:18:53 +00:00
// flags always start with a -
if !strings.HasPrefix(arg, "-") {
return false
}
2019-09-12 03:28:48 +00:00
// this line turns `--flag` into `flag`
2019-10-02 03:13:04 +00:00
if strings.HasPrefix(arg, "--") {
arg = strings.Replace(arg, "-", "", 2)
}
// this line turns `-flag` into `flag`
if strings.HasPrefix(arg, "-") {
arg = strings.Replace(arg, "-", "", 1)
}
2019-09-12 03:28:48 +00:00
// this line turns `flag=value` into `flag`
2019-09-12 03:24:42 +00:00
arg = strings.Split(arg, "=")[0]
2019-09-12 03:28:48 +00:00
// look through all the flags, to see if the `arg` is one of our flags
2019-09-12 02:25:51 +00:00
for _, flag := range commandFlags {
for _, key := range strings.Split(flag.GetName(), ",") {
2019-10-02 03:21:12 +00:00
key := strings.TrimSpace(key)
2019-09-12 03:24:42 +00:00
if key == arg {
2019-09-12 02:25:51 +00:00
return true
}
}
}
2019-09-12 03:29:31 +00:00
// return false if this arg was not one of our flags
2019-09-12 02:25:51 +00:00
return false
}
// Names returns the names including short names and aliases.
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// HasName returns true if Command.Name or Command.ShortName matches given name
2013-07-20 15:21:20 +00:00
func (c Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
}
}
return false
2013-07-19 02:30:18 +00:00
}
func (c Command) startApp(ctx *Context) error {
app := NewApp()
2016-04-30 02:42:07 +00:00
app.Metadata = ctx.App.Metadata
app.ExitErrHandler = ctx.App.ExitErrHandler
// set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
2015-08-13 04:58:25 +00:00
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
2016-11-25 08:16:48 +00:00
app.CustomAppHelpTemplate = c.CustomHelpTemplate
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
2014-07-13 13:16:30 +00:00
app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter
2019-08-05 10:16:30 +00:00
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
app.categories = CommandCategories{}
for _, command := range c.Subcommands {
app.categories = app.categories.AddCommand(command.Category, command)
}
sort.Sort(app.categories)
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
2014-11-18 22:44:21 +00:00
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)
}