github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/cli/command.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "sort" 7 "strings" 8 ) 9 10 // Command is a subcommand for a cli.App. 11 type Command struct { 12 // The name of the command 13 Name string 14 // short name of the command. Typically one character (deprecated, use `Aliases`) 15 ShortName string 16 // A list of aliases for the command 17 Aliases []string 18 // A short description of the usage of this command 19 Usage string 20 // Custom text to show on USAGE section of help 21 UsageText string 22 // A longer explanation of how the command works 23 Description string 24 // A short description of the arguments of this command 25 ArgsUsage string 26 // The category the command is part of 27 Category string 28 // The function to call when checking for bash command completions 29 BashComplete func(context *Context) 30 // An action to execute before any sub-subcommands are run, but after the context is ready 31 // If a non-nil error is returned, no sub-subcommands are run 32 Before func(context *Context) error 33 // An action to execute after any subcommands are run, but before the subcommand has finished 34 // It is run even if Action() panics 35 After func(context *Context) error 36 // The function to call when this command is invoked 37 Action func(context *Context) 38 // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. 39 // This function is able to replace the original error messages. 40 // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. 41 OnUsageError func(context *Context, err error) error 42 // List of child commands 43 Subcommands Commands 44 // List of flags to parse 45 Flags []Flag 46 // Treat all flags as normal arguments if true 47 SkipFlagParsing bool 48 // Boolean to hide built-in help command 49 HideHelp bool 50 51 // Full name of command for help, defaults to full command name, including parent commands. 52 HelpName string 53 commandNamePath []string 54 } 55 56 // Returns the full name of the command. 57 // For subcommands this ensures that parent commands are part of the command path 58 func (c Command) FullName() string { 59 if c.commandNamePath == nil { 60 return c.Name 61 } 62 return strings.Join(c.commandNamePath, " ") 63 } 64 65 type Commands []Command 66 67 // Invokes the command given the context, parses ctx.Args() to generate command-specific flags 68 func (c Command) Run(ctx *Context) (err error) { 69 if len(c.Subcommands) > 0 { 70 return c.startApp(ctx) 71 } 72 73 if !c.HideHelp && (HelpFlag != BoolFlag{}) { 74 // append help to flags 75 c.Flags = append( 76 c.Flags, 77 HelpFlag, 78 ) 79 } 80 81 if ctx.App.EnableBashCompletion { 82 c.Flags = append(c.Flags, BashCompletionFlag) 83 } 84 85 set := flagSet(c.Name, c.Flags) 86 set.SetOutput(ioutil.Discard) 87 88 if !c.SkipFlagParsing { 89 firstFlagIndex := -1 90 terminatorIndex := -1 91 for index, arg := range ctx.Args() { 92 if arg == "--" { 93 terminatorIndex = index 94 break 95 } else if arg == "-" { 96 // Do nothing. A dash alone is not really a flag. 97 continue 98 } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { 99 firstFlagIndex = index 100 } 101 } 102 103 if firstFlagIndex > -1 { 104 args := ctx.Args() 105 regularArgs := make([]string, len(args[1:firstFlagIndex])) 106 copy(regularArgs, args[1:firstFlagIndex]) 107 108 var flagArgs []string 109 if terminatorIndex > -1 { 110 flagArgs = args[firstFlagIndex:terminatorIndex] 111 regularArgs = append(regularArgs, args[terminatorIndex:]...) 112 } else { 113 flagArgs = args[firstFlagIndex:] 114 } 115 116 err = set.Parse(append(flagArgs, regularArgs...)) 117 } else { 118 err = set.Parse(ctx.Args().Tail()) 119 } 120 } else { 121 if c.SkipFlagParsing { 122 err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) 123 } 124 } 125 126 if err != nil { 127 if c.OnUsageError != nil { 128 err := c.OnUsageError(ctx, err) 129 return err 130 } else { 131 fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") 132 fmt.Fprintln(ctx.App.Writer) 133 ShowCommandHelp(ctx, c.Name) 134 return err 135 } 136 } 137 138 nerr := normalizeFlags(c.Flags, set) 139 if nerr != nil { 140 fmt.Fprintln(ctx.App.Writer, nerr) 141 fmt.Fprintln(ctx.App.Writer) 142 ShowCommandHelp(ctx, c.Name) 143 return nerr 144 } 145 context := NewContext(ctx.App, set, ctx) 146 147 if checkCommandCompletions(context, c.Name) { 148 return nil 149 } 150 151 if checkCommandHelp(context, c.Name) { 152 return nil 153 } 154 155 if c.After != nil { 156 defer func() { 157 afterErr := c.After(context) 158 if afterErr != nil { 159 if err != nil { 160 err = NewMultiError(err, afterErr) 161 } else { 162 err = afterErr 163 } 164 } 165 }() 166 } 167 168 if c.Before != nil { 169 err := c.Before(context) 170 if err != nil { 171 fmt.Fprintln(ctx.App.Writer, err) 172 fmt.Fprintln(ctx.App.Writer) 173 ShowCommandHelp(ctx, c.Name) 174 return err 175 } 176 } 177 178 context.Command = c 179 c.Action(context) 180 return nil 181 } 182 183 func (c Command) Names() []string { 184 names := []string{c.Name} 185 186 if c.ShortName != "" { 187 names = append(names, c.ShortName) 188 } 189 190 return append(names, c.Aliases...) 191 } 192 193 // Returns true if Command.Name or Command.ShortName matches given name 194 func (c Command) HasName(name string) bool { 195 for _, n := range c.Names() { 196 if n == name { 197 return true 198 } 199 } 200 return false 201 } 202 203 func (c Command) startApp(ctx *Context) error { 204 app := NewApp() 205 206 // set the name and usage 207 app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) 208 if c.HelpName == "" { 209 app.HelpName = c.HelpName 210 } else { 211 app.HelpName = app.Name 212 } 213 214 if c.Description != "" { 215 app.Usage = c.Description 216 } else { 217 app.Usage = c.Usage 218 } 219 220 // set CommandNotFound 221 app.CommandNotFound = ctx.App.CommandNotFound 222 223 // set the flags and commands 224 app.Commands = c.Subcommands 225 app.Flags = c.Flags 226 app.HideHelp = c.HideHelp 227 228 app.Version = ctx.App.Version 229 app.HideVersion = ctx.App.HideVersion 230 app.Compiled = ctx.App.Compiled 231 app.Author = ctx.App.Author 232 app.Email = ctx.App.Email 233 app.Writer = ctx.App.Writer 234 235 app.categories = CommandCategories{} 236 for _, command := range c.Subcommands { 237 app.categories = app.categories.AddCommand(command.Category, command) 238 } 239 240 sort.Sort(app.categories) 241 242 // bash completion 243 app.EnableBashCompletion = ctx.App.EnableBashCompletion 244 if c.BashComplete != nil { 245 app.BashComplete = c.BashComplete 246 } 247 248 // set the actions 249 app.Before = c.Before 250 app.After = c.After 251 if c.Action != nil { 252 app.Action = c.Action 253 } else { 254 app.Action = helpSubcommand.Action 255 } 256 257 for index, cc := range app.Commands { 258 app.Commands[index].commandNamePath = []string{c.Name, cc.Name} 259 } 260 261 return app.RunAsSubcommand(ctx) 262 }