2013-07-19 00:29:06 +00:00
|
|
|
package cli
|
|
|
|
|
2013-09-04 19:58:31 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
2013-09-18 16:24:20 +00:00
|
|
|
"io/ioutil"
|
2016-03-20 19:17:13 +00:00
|
|
|
"sort"
|
2013-09-18 16:24:20 +00:00
|
|
|
"strings"
|
2013-09-04 19:58:31 +00:00
|
|
|
)
|
|
|
|
|
2013-11-01 14:31:37 +00:00
|
|
|
// Command is a subcommand for a cli.App.
|
2013-07-19 00:29:06 +00:00
|
|
|
type Command struct {
|
2013-11-01 14:31:37 +00:00
|
|
|
// The name of the command
|
|
|
|
Name string
|
2015-03-10 04:24:57 +00:00
|
|
|
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
2013-11-01 14:31:37 +00:00
|
|
|
ShortName string
|
2015-03-10 04:24:57 +00:00
|
|
|
// 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
|
2015-10-25 05:37:21 +00:00
|
|
|
// Custom text to show on USAGE section of help
|
|
|
|
UsageText string
|
2014-04-16 16:18:00 +00:00
|
|
|
// 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
|
2016-04-25 22:29:05 +00:00
|
|
|
BashComplete BashCompleteFunc
|
2014-04-16 20:26:28 +00:00
|
|
|
// 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
|
2016-04-25 22:29:05 +00:00
|
|
|
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
|
2015-02-20 21:21:27 +00:00
|
|
|
// It is run even if Action() panics
|
2016-04-25 22:29:05 +00:00
|
|
|
After AfterFunc
|
2013-11-01 14:31:37 +00:00
|
|
|
// The function to call when this command is invoked
|
2016-04-27 15:34:01 +00:00
|
|
|
Action interface{}
|
|
|
|
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
|
|
|
// of deprecation period has passed, maybe?
|
|
|
|
|
2016-04-25 22:29:05 +00:00
|
|
|
// Execute this function if a usage error occurs.
|
|
|
|
OnUsageError OnUsageErrorFunc
|
2014-04-16 16:18:00 +00:00
|
|
|
// List of child commands
|
2014-12-15 22:35:49 +00:00
|
|
|
Subcommands Commands
|
2014-04-16 20:26:28 +00:00
|
|
|
// 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
|
2016-09-11 19:04:35 +00:00
|
|
|
// 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
|
2015-02-06 08:46:32 +00:00
|
|
|
// Boolean to hide this command from help or completion
|
|
|
|
Hidden bool
|
2017-11-13 21:28:23 +00:00
|
|
|
// Boolean to enable short-option handling so user can combine several
|
|
|
|
// single-character bool arguements into one
|
|
|
|
// i.e. foobar -o -v -> foobar -ov
|
|
|
|
UseShortOptionHandling bool
|
2015-06-20 23:59:54 +00:00
|
|
|
|
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
|
2015-06-20 23:59:54 +00:00
|
|
|
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
|
2015-06-20 23:59:54 +00:00
|
|
|
}
|
|
|
|
|
2016-12-18 16:43:48 +00:00
|
|
|
type CommandsByName []Command
|
|
|
|
|
|
|
|
func (c CommandsByName) Len() int {
|
|
|
|
return len(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c CommandsByName) Less(i, j int) bool {
|
2017-10-28 07:00:11 +00:00
|
|
|
return lexicographicLess(c[i].Name, c[j].Name)
|
2016-12-18 16:43:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c CommandsByName) Swap(i, j int) {
|
|
|
|
c[i], c[j] = c[j], c[i]
|
|
|
|
}
|
|
|
|
|
2016-05-09 14:12:59 +00:00
|
|
|
// FullName returns the full name of the command.
|
2015-06-20 23:59:54 +00:00
|
|
|
// 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 00:29:06 +00:00
|
|
|
}
|
2013-07-19 02:23:00 +00:00
|
|
|
|
2016-05-09 14:12:59 +00:00
|
|
|
// Commands is a slice of Command
|
2014-12-15 22:35:49 +00:00
|
|
|
type Commands []Command
|
|
|
|
|
2016-05-09 14:12:59 +00:00
|
|
|
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
2016-04-27 13:12:34 +00:00
|
|
|
func (c Command) Run(ctx *Context) (err error) {
|
2015-12-25 20:45:58 +00:00
|
|
|
if len(c.Subcommands) > 0 {
|
2014-04-16 16:18:00 +00:00
|
|
|
return c.startApp(ctx)
|
|
|
|
}
|
|
|
|
|
2014-12-02 04:20:21 +00:00
|
|
|
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
|
|
|
|
2016-09-17 23:54:29 +00:00
|
|
|
set, err := flagSet(c.Name, c.Flags)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-09-04 19:58:31 +00:00
|
|
|
set.SetOutput(ioutil.Discard)
|
2017-11-20 15:32:03 +00:00
|
|
|
firstFlagIndex, terminatorIndex := getIndexes(ctx)
|
|
|
|
flagArgs, regularArgs := getAllArgs(ctx.Args(), firstFlagIndex, terminatorIndex)
|
|
|
|
if c.UseShortOptionHandling {
|
|
|
|
flagArgs = translateShortOptions(flagArgs)
|
|
|
|
}
|
2016-09-11 19:04:35 +00:00
|
|
|
if c.SkipFlagParsing {
|
|
|
|
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
|
|
|
} else if !c.SkipArgReorder {
|
2015-10-17 18:36:09 +00:00
|
|
|
if firstFlagIndex > -1 {
|
2017-11-20 15:32:03 +00:00
|
|
|
err = set.Parse(append(flagArgs, regularArgs...))
|
2015-10-17 18:36:09 +00:00
|
|
|
} else {
|
|
|
|
err = set.Parse(ctx.Args().Tail())
|
2015-09-25 21:13:36 +00:00
|
|
|
}
|
2017-11-20 15:32:03 +00:00
|
|
|
} else if c.UseShortOptionHandling {
|
|
|
|
if terminatorIndex == -1 && firstFlagIndex > -1 {
|
|
|
|
// Handle shortname AND no options
|
|
|
|
err = set.Parse(append(regularArgs, flagArgs...))
|
|
|
|
} else {
|
|
|
|
// Handle shortname and options
|
|
|
|
err = set.Parse(flagArgs)
|
|
|
|
}
|
2016-09-12 13:30:33 +00:00
|
|
|
} else {
|
2017-11-20 15:32:03 +00:00
|
|
|
err = set.Parse(append(regularArgs, flagArgs...))
|
2013-09-18 16:24:20 +00:00
|
|
|
}
|
2013-09-04 19:58:31 +00:00
|
|
|
|
2013-11-20 08:05:18 +00:00
|
|
|
nerr := normalizeFlags(c.Flags, set)
|
|
|
|
if nerr != nil {
|
2014-12-02 04:57:35 +00:00
|
|
|
fmt.Fprintln(ctx.App.Writer, nerr)
|
|
|
|
fmt.Fprintln(ctx.App.Writer)
|
2013-11-20 08:05:18 +00:00
|
|
|
ShowCommandHelp(ctx, c.Name)
|
2016-04-27 13:12:34 +00:00
|
|
|
return nerr
|
2013-11-20 08:05:18 +00:00
|
|
|
}
|
2016-04-25 22:10:10 +00:00
|
|
|
|
2015-05-18 15:39:48 +00:00
|
|
|
context := NewContext(ctx.App, set, ctx)
|
2017-04-24 22:19:34 +00:00
|
|
|
context.Command = c
|
2014-04-10 17:14:13 +00:00
|
|
|
if checkCommandCompletions(context, c.Name) {
|
2016-04-27 13:12:34 +00:00
|
|
|
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 {
|
2017-04-24 22:19:34 +00:00
|
|
|
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
|
|
|
|
}
|
2017-04-24 22:19:34 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-11-01 13:45:19 +00:00
|
|
|
if checkCommandHelp(context, c.Name) {
|
2016-04-27 13:12:34 +00:00
|
|
|
return nil
|
2013-11-01 13:45:19 +00:00
|
|
|
}
|
2015-12-25 20:45:58 +00:00
|
|
|
|
|
|
|
if c.After != nil {
|
|
|
|
defer func() {
|
2016-04-27 13:12:34 +00:00
|
|
|
afterErr := c.After(context)
|
2015-12-25 20:45:58 +00:00
|
|
|
if afterErr != nil {
|
2017-06-28 16:52:12 +00:00
|
|
|
context.App.handleExitCoder(context, err)
|
2015-12-25 20:45:58 +00:00
|
|
|
if err != nil {
|
|
|
|
err = NewMultiError(err, afterErr)
|
|
|
|
} else {
|
|
|
|
err = afterErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Before != nil {
|
2016-04-27 13:12:34 +00:00
|
|
|
err = c.Before(context)
|
2015-12-25 20:45:58 +00:00
|
|
|
if err != nil {
|
2017-04-24 22:19:34 +00:00
|
|
|
ShowCommandHelp(context, c.Name)
|
2017-06-28 16:52:12 +00:00
|
|
|
context.App.handleExitCoder(context, err)
|
2016-04-27 13:12:34 +00:00
|
|
|
return err
|
2015-12-25 20:45:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-12 21:37:07 +00:00
|
|
|
if c.Action == nil {
|
|
|
|
c.Action = helpSubcommand.Action
|
|
|
|
}
|
|
|
|
|
2016-04-27 15:34:01 +00:00
|
|
|
err = HandleAction(c.Action, context)
|
|
|
|
|
2016-04-27 13:12:34 +00:00
|
|
|
if err != nil {
|
2017-06-28 16:52:12 +00:00
|
|
|
context.App.handleExitCoder(context, err)
|
2016-04-27 13:12:34 +00:00
|
|
|
}
|
|
|
|
return err
|
2013-07-19 02:23:00 +00:00
|
|
|
}
|
2013-07-19 02:30:18 +00:00
|
|
|
|
2017-11-20 15:32:03 +00:00
|
|
|
func getIndexes(ctx *Context) (int, int) {
|
|
|
|
firstFlagIndex := -1
|
|
|
|
terminatorIndex := -1
|
|
|
|
for index, arg := range ctx.Args() {
|
|
|
|
if arg == "--" {
|
|
|
|
terminatorIndex = index
|
|
|
|
break
|
|
|
|
} else if arg == "-" {
|
|
|
|
// Do nothing. A dash alone is not really a flag.
|
|
|
|
continue
|
|
|
|
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
|
|
|
firstFlagIndex = index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(ctx.Args()) > 0 && !strings.HasPrefix(ctx.Args()[0], "-") && firstFlagIndex == -1 {
|
|
|
|
return -1, -1
|
|
|
|
}
|
|
|
|
|
|
|
|
return firstFlagIndex, terminatorIndex
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// copyStringslice takes a string slice and copies it
|
|
|
|
func copyStringSlice(slice []string, start, end int) []string {
|
|
|
|
newSlice := make([]string, end-start)
|
|
|
|
copy(newSlice, slice[start:end])
|
|
|
|
return newSlice
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAllArgs extracts and returns two string slices representing
|
|
|
|
// regularArgs and flagArgs
|
|
|
|
func getAllArgs(args []string, firstFlagIndex, terminatorIndex int) ([]string, []string) {
|
|
|
|
var regularArgs []string
|
|
|
|
// if there are no options, the we set the index to 1 manually
|
|
|
|
if firstFlagIndex == -1 {
|
|
|
|
firstFlagIndex = 1
|
|
|
|
regularArgs = copyStringSlice(args, 0, len(args))
|
|
|
|
} else {
|
|
|
|
regularArgs = copyStringSlice(args, 1, firstFlagIndex)
|
|
|
|
}
|
|
|
|
var flagArgs []string
|
|
|
|
// a flag terminatorIndex was found in the input. we need to collect
|
|
|
|
// flagArgs based on it.
|
|
|
|
if terminatorIndex > -1 {
|
|
|
|
flagArgs = copyStringSlice(args, firstFlagIndex, terminatorIndex)
|
|
|
|
additionalRegularArgs := copyStringSlice(args, terminatorIndex, len(args))
|
|
|
|
regularArgs = append(regularArgs, additionalRegularArgs...)
|
|
|
|
for _, i := range additionalRegularArgs {
|
|
|
|
regularArgs = append(regularArgs, i)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
flagArgs = args[firstFlagIndex:]
|
|
|
|
}
|
|
|
|
return flagArgs, regularArgs
|
|
|
|
}
|
|
|
|
|
|
|
|
func translateShortOptions(flagArgs Args) []string {
|
|
|
|
// separate combined flags
|
|
|
|
var flagArgsSeparated []string
|
|
|
|
for _, flagArg := range flagArgs {
|
|
|
|
if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
|
|
|
|
for _, flagChar := range flagArg[1:] {
|
|
|
|
flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
flagArgsSeparated = append(flagArgsSeparated, flagArg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return flagArgsSeparated
|
|
|
|
}
|
|
|
|
|
2016-05-09 14:12:59 +00:00
|
|
|
// Names returns the names including short names and aliases.
|
2015-03-10 04:24:57 +00:00
|
|
|
func (c Command) Names() []string {
|
|
|
|
names := []string{c.Name}
|
|
|
|
|
|
|
|
if c.ShortName != "" {
|
|
|
|
names = append(names, c.ShortName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return append(names, c.Aliases...)
|
|
|
|
}
|
|
|
|
|
2016-05-09 14:12:59 +00:00
|
|
|
// 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 {
|
2015-03-10 04:24:57 +00:00
|
|
|
for _, n := range c.Names() {
|
|
|
|
if n == name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2013-07-19 02:30:18 +00:00
|
|
|
}
|
2014-04-16 16:18:00 +00:00
|
|
|
|
2016-04-27 13:12:34 +00:00
|
|
|
func (c Command) startApp(ctx *Context) error {
|
2014-04-16 16:18:00 +00:00
|
|
|
app := NewApp()
|
2016-04-30 02:42:07 +00:00
|
|
|
app.Metadata = ctx.App.Metadata
|
2014-04-16 16:18:00 +00:00
|
|
|
// 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 {
|
2016-02-04 07:25:41 +00:00
|
|
|
app.HelpName = app.Name
|
2015-08-13 04:58:25 +00:00
|
|
|
}
|
|
|
|
|
2017-01-09 23:57:49 +00:00
|
|
|
app.Usage = c.Usage
|
|
|
|
app.Description = c.Description
|
|
|
|
app.ArgsUsage = c.ArgsUsage
|
2014-04-16 16:18:00 +00:00
|
|
|
|
2014-07-25 17:06:04 +00:00
|
|
|
// set CommandNotFound
|
|
|
|
app.CommandNotFound = ctx.App.CommandNotFound
|
2016-11-25 08:16:48 +00:00
|
|
|
app.CustomAppHelpTemplate = c.CustomHelpTemplate
|
2014-07-25 17:06:04 +00:00
|
|
|
|
2014-04-16 16:18:00 +00:00
|
|
|
// 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
|
2015-05-04 01:42:21 +00:00
|
|
|
|
|
|
|
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
|
2015-05-04 01:37:51 +00:00
|
|
|
app.Writer = ctx.App.Writer
|
2017-01-19 04:29:26 +00:00
|
|
|
app.ErrWriter = ctx.App.ErrWriter
|
2014-04-16 16:18:00 +00:00
|
|
|
|
2016-03-20 19:17:13 +00:00
|
|
|
app.categories = CommandCategories{}
|
|
|
|
for _, command := range c.Subcommands {
|
|
|
|
app.categories = app.categories.AddCommand(command.Category, command)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(app.categories)
|
|
|
|
|
2014-04-16 16:18:00 +00:00
|
|
|
// bash completion
|
|
|
|
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
|
|
|
if c.BashComplete != nil {
|
|
|
|
app.BashComplete = c.BashComplete
|
|
|
|
}
|
|
|
|
|
2014-04-16 20:26:28 +00:00
|
|
|
// set the actions
|
|
|
|
app.Before = c.Before
|
2014-11-18 22:44:21 +00:00
|
|
|
app.After = c.After
|
2014-04-16 16:18:00 +00:00
|
|
|
if c.Action != nil {
|
|
|
|
app.Action = c.Action
|
|
|
|
} else {
|
|
|
|
app.Action = helpSubcommand.Action
|
|
|
|
}
|
2017-05-05 22:01:44 +00:00
|
|
|
app.OnUsageError = c.OnUsageError
|
2014-04-16 16:18:00 +00:00
|
|
|
|
2016-02-04 07:25:41 +00:00
|
|
|
for index, cc := range app.Commands {
|
|
|
|
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
2015-06-20 23:59:54 +00:00
|
|
|
}
|
|
|
|
|
2014-04-16 16:18:00 +00:00
|
|
|
return app.RunAsSubcommand(ctx)
|
|
|
|
}
|
2016-05-01 12:36:17 +00:00
|
|
|
|
|
|
|
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
|
|
func (c Command) VisibleFlags() []Flag {
|
|
|
|
return visibleFlags(c.Flags)
|
|
|
|
}
|