mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
6.9 KiB
286 lines
6.9 KiB
package cli |
|
|
|
import ( |
|
"fmt" |
|
"io/ioutil" |
|
"sort" |
|
"strings" |
|
) |
|
|
|
// Command is a subcommand for a cli.App. |
|
type Command struct { |
|
// The name of the command |
|
Name string |
|
// short name of the command. Typically one character (deprecated, use `Aliases`) |
|
ShortName 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 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 |
|
Subcommands Commands |
|
// List of flags to parse |
|
Flags []Flag |
|
// 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 |
|
// Boolean to hide built-in help command |
|
HideHelp bool |
|
// Boolean to hide this command from help or completion |
|
Hidden bool |
|
|
|
// Full name of command for help, defaults to full command name, including parent commands. |
|
HelpName string |
|
commandNamePath []string |
|
} |
|
|
|
// 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, " ") |
|
} |
|
|
|
// Commands is a slice of Command |
|
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{}) { |
|
// append help to flags |
|
c.Flags = append( |
|
c.Flags, |
|
HelpFlag, |
|
) |
|
} |
|
|
|
set, err := flagSet(c.Name, c.Flags) |
|
if err != nil { |
|
return err |
|
} |
|
set.SetOutput(ioutil.Discard) |
|
|
|
if c.SkipFlagParsing { |
|
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) |
|
} else if !c.SkipArgReorder { |
|
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 firstFlagIndex > -1 { |
|
args := ctx.Args() |
|
regularArgs := make([]string, len(args[1:firstFlagIndex])) |
|
copy(regularArgs, args[1:firstFlagIndex]) |
|
|
|
var flagArgs []string |
|
if terminatorIndex > -1 { |
|
flagArgs = args[firstFlagIndex:terminatorIndex] |
|
regularArgs = append(regularArgs, args[terminatorIndex:]...) |
|
} else { |
|
flagArgs = args[firstFlagIndex:] |
|
} |
|
|
|
err = set.Parse(append(flagArgs, regularArgs...)) |
|
} else { |
|
err = set.Parse(ctx.Args().Tail()) |
|
} |
|
} else { |
|
err = set.Parse(ctx.Args().Tail()) |
|
} |
|
|
|
nerr := normalizeFlags(c.Flags, set) |
|
if nerr != nil { |
|
fmt.Fprintln(ctx.App.Writer, nerr) |
|
fmt.Fprintln(ctx.App.Writer) |
|
ShowCommandHelp(ctx, c.Name) |
|
return nerr |
|
} |
|
|
|
context := NewContext(ctx.App, set, ctx) |
|
if checkCommandCompletions(context, c.Name) { |
|
return nil |
|
} |
|
|
|
if err != nil { |
|
if c.OnUsageError != nil { |
|
err := c.OnUsageError(ctx, err, false) |
|
HandleExitCoder(err) |
|
return err |
|
} |
|
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error()) |
|
fmt.Fprintln(ctx.App.Writer) |
|
ShowCommandHelp(ctx, c.Name) |
|
return err |
|
} |
|
|
|
if checkCommandHelp(context, c.Name) { |
|
return nil |
|
} |
|
|
|
if c.After != nil { |
|
defer func() { |
|
afterErr := c.After(context) |
|
if afterErr != nil { |
|
HandleExitCoder(err) |
|
if err != nil { |
|
err = NewMultiError(err, afterErr) |
|
} else { |
|
err = afterErr |
|
} |
|
} |
|
}() |
|
} |
|
|
|
if c.Before != nil { |
|
err = c.Before(context) |
|
if err != nil { |
|
fmt.Fprintln(ctx.App.Writer, err) |
|
fmt.Fprintln(ctx.App.Writer) |
|
ShowCommandHelp(ctx, c.Name) |
|
HandleExitCoder(err) |
|
return err |
|
} |
|
} |
|
|
|
if c.Action == nil { |
|
c.Action = helpSubcommand.Action |
|
} |
|
|
|
context.Command = c |
|
err = HandleAction(c.Action, context) |
|
|
|
if err != nil { |
|
HandleExitCoder(err) |
|
} |
|
return err |
|
} |
|
|
|
// 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 |
|
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 := NewApp() |
|
app.Metadata = ctx.App.Metadata |
|
// set the name and usage |
|
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) |
|
if c.HelpName == "" { |
|
app.HelpName = c.HelpName |
|
} else { |
|
app.HelpName = app.Name |
|
} |
|
|
|
if c.Description != "" { |
|
app.Usage = c.Description |
|
} else { |
|
app.Usage = c.Usage |
|
} |
|
|
|
// set CommandNotFound |
|
app.CommandNotFound = ctx.App.CommandNotFound |
|
|
|
// set the flags and commands |
|
app.Commands = c.Subcommands |
|
app.Flags = c.Flags |
|
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.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 |
|
app.After = c.After |
|
if c.Action != nil { |
|
app.Action = c.Action |
|
} else { |
|
app.Action = helpSubcommand.Action |
|
} |
|
|
|
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) |
|
}
|
|
|