github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/cli/app.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "sort" 10 "time" 11 ) 12 13 // App is the main structure of a cli application. It is recommended that 14 // an app be created with the cli.NewApp() function 15 type App struct { 16 // The name of the program. Defaults to path.Base(os.Args[0]) 17 Name string 18 // Full name of command for help, defaults to Name 19 HelpName string 20 // Description of the program. 21 Usage string 22 // Text to override the USAGE section of help 23 UsageText string 24 // Description of the program argument format. 25 ArgsUsage string 26 // Version of the program 27 Version string 28 // List of commands to execute 29 Commands []Command 30 // List of flags to parse 31 Flags []Flag 32 // Boolean to enable bash completion commands 33 EnableBashCompletion bool 34 // Boolean to hide built-in help command 35 HideHelp bool 36 // Boolean to hide built-in version flag and the VERSION section of help 37 HideVersion bool 38 // Populate on app startup, only gettable throught method Categories() 39 categories CommandCategories 40 // An action to execute when the bash-completion flag is set 41 BashComplete func(context *Context) 42 // An action to execute before any subcommands are run, but after the context is ready 43 // If a non-nil error is returned, no subcommands are run 44 Before func(context *Context) error 45 // An action to execute after any subcommands are run, but after the subcommand has finished 46 // It is run even if Action() panics 47 After func(context *Context) error 48 // The action to execute when no subcommands are specified 49 Action func(context *Context) 50 // Execute this function if the proper command cannot be found 51 CommandNotFound func(context *Context, command string) 52 // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. 53 // This function is able to replace the original error messages. 54 // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. 55 OnUsageError func(context *Context, err error, isSubcommand bool) error 56 // Compilation date 57 Compiled time.Time 58 // List of all authors who contributed 59 Authors []Author 60 // Copyright of the binary if any 61 Copyright string 62 // Name of Author (Note: Use App.Authors, this is deprecated) 63 Author string 64 // Email of Author (Note: Use App.Authors, this is deprecated) 65 Email string 66 // Writer writer to write output to 67 Writer io.Writer 68 } 69 70 // Tries to find out when this binary was compiled. 71 // Returns the current time if it fails to find it. 72 func compileTime() time.Time { 73 info, err := os.Stat(os.Args[0]) 74 if err != nil { 75 return time.Now() 76 } 77 return info.ModTime() 78 } 79 80 // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. 81 func NewApp() *App { 82 return &App{ 83 Name: filepath.Base(os.Args[0]), 84 HelpName: filepath.Base(os.Args[0]), 85 Usage: "A new cli application", 86 UsageText: "", 87 Version: "0.0.0", 88 BashComplete: DefaultAppComplete, 89 Action: helpCommand.Action, 90 Compiled: compileTime(), 91 Writer: os.Stdout, 92 } 93 } 94 95 // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination 96 func (a *App) Run(arguments []string) (err error) { 97 if a.Author != "" || a.Email != "" { 98 a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) 99 } 100 101 newCmds := []Command{} 102 for _, c := range a.Commands { 103 if c.HelpName == "" { 104 c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 105 } 106 newCmds = append(newCmds, c) 107 } 108 a.Commands = newCmds 109 110 a.categories = CommandCategories{} 111 for _, command := range a.Commands { 112 a.categories = a.categories.AddCommand(command.Category, command) 113 } 114 sort.Sort(a.categories) 115 116 // append help to commands 117 if a.Command(helpCommand.Name) == nil && !a.HideHelp { 118 a.Commands = append(a.Commands, helpCommand) 119 if (HelpFlag != BoolFlag{}) { 120 a.appendFlag(HelpFlag) 121 } 122 } 123 124 //append version/help flags 125 if a.EnableBashCompletion { 126 a.appendFlag(BashCompletionFlag) 127 } 128 129 if !a.HideVersion { 130 a.appendFlag(VersionFlag) 131 } 132 133 // parse flags 134 set := flagSet(a.Name, a.Flags) 135 set.SetOutput(ioutil.Discard) 136 err = set.Parse(arguments[1:]) 137 nerr := normalizeFlags(a.Flags, set) 138 context := NewContext(a, set, nil) 139 if nerr != nil { 140 fmt.Fprintln(a.Writer, nerr) 141 ShowAppHelp(context) 142 return nerr 143 } 144 145 if checkCompletions(context) { 146 return nil 147 } 148 149 if err != nil { 150 if a.OnUsageError != nil { 151 err := a.OnUsageError(context, err, false) 152 return err 153 } else { 154 fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") 155 ShowAppHelp(context) 156 return err 157 } 158 } 159 160 if !a.HideHelp && checkHelp(context) { 161 ShowAppHelp(context) 162 return nil 163 } 164 165 if !a.HideVersion && checkVersion(context) { 166 ShowVersion(context) 167 return nil 168 } 169 170 if a.After != nil { 171 defer func() { 172 if afterErr := a.After(context); afterErr != nil { 173 if err != nil { 174 err = NewMultiError(err, afterErr) 175 } else { 176 err = afterErr 177 } 178 } 179 }() 180 } 181 182 if a.Before != nil { 183 err = a.Before(context) 184 if err != nil { 185 fmt.Fprintf(a.Writer, "%v\n\n", err) 186 ShowAppHelp(context) 187 return err 188 } 189 } 190 191 args := context.Args() 192 if args.Present() { 193 name := args.First() 194 c := a.Command(name) 195 if c != nil { 196 return c.Run(context) 197 } 198 } 199 200 // Run default Action 201 a.Action(context) 202 return nil 203 } 204 205 // Another entry point to the cli app, takes care of passing arguments and error handling 206 func (a *App) RunAndExitOnError() { 207 if err := a.Run(os.Args); err != nil { 208 fmt.Fprintln(os.Stderr, err) 209 os.Exit(1) 210 } 211 } 212 213 // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags 214 func (a *App) RunAsSubcommand(ctx *Context) (err error) { 215 // append help to commands 216 if len(a.Commands) > 0 { 217 if a.Command(helpCommand.Name) == nil && !a.HideHelp { 218 a.Commands = append(a.Commands, helpCommand) 219 if (HelpFlag != BoolFlag{}) { 220 a.appendFlag(HelpFlag) 221 } 222 } 223 } 224 225 newCmds := []Command{} 226 for _, c := range a.Commands { 227 if c.HelpName == "" { 228 c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 229 } 230 newCmds = append(newCmds, c) 231 } 232 a.Commands = newCmds 233 234 // append flags 235 if a.EnableBashCompletion { 236 a.appendFlag(BashCompletionFlag) 237 } 238 239 // parse flags 240 set := flagSet(a.Name, a.Flags) 241 set.SetOutput(ioutil.Discard) 242 err = set.Parse(ctx.Args().Tail()) 243 nerr := normalizeFlags(a.Flags, set) 244 context := NewContext(a, set, ctx) 245 246 if nerr != nil { 247 fmt.Fprintln(a.Writer, nerr) 248 fmt.Fprintln(a.Writer) 249 if len(a.Commands) > 0 { 250 ShowSubcommandHelp(context) 251 } else { 252 ShowCommandHelp(ctx, context.Args().First()) 253 } 254 return nerr 255 } 256 257 if checkCompletions(context) { 258 return nil 259 } 260 261 if err != nil { 262 if a.OnUsageError != nil { 263 err = a.OnUsageError(context, err, true) 264 return err 265 } else { 266 fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") 267 ShowSubcommandHelp(context) 268 return err 269 } 270 } 271 272 if len(a.Commands) > 0 { 273 if checkSubcommandHelp(context) { 274 return nil 275 } 276 } else { 277 if checkCommandHelp(ctx, context.Args().First()) { 278 return nil 279 } 280 } 281 282 if a.After != nil { 283 defer func() { 284 afterErr := a.After(context) 285 if afterErr != nil { 286 if err != nil { 287 err = NewMultiError(err, afterErr) 288 } else { 289 err = afterErr 290 } 291 } 292 }() 293 } 294 295 if a.Before != nil { 296 err := a.Before(context) 297 if err != nil { 298 return err 299 } 300 } 301 302 args := context.Args() 303 if args.Present() { 304 name := args.First() 305 c := a.Command(name) 306 if c != nil { 307 return c.Run(context) 308 } 309 } 310 311 // Run default Action 312 a.Action(context) 313 314 return nil 315 } 316 317 // Returns the named command on App. Returns nil if the command does not exist 318 func (a *App) Command(name string) *Command { 319 for _, c := range a.Commands { 320 if c.HasName(name) { 321 return &c 322 } 323 } 324 325 return nil 326 } 327 328 // Returnes the array containing all the categories with the commands they contain 329 func (a *App) Categories() CommandCategories { 330 return a.categories 331 } 332 333 func (a *App) hasFlag(flag Flag) bool { 334 for _, f := range a.Flags { 335 if flag == f { 336 return true 337 } 338 } 339 340 return false 341 } 342 343 func (a *App) appendFlag(flag Flag) { 344 if !a.hasFlag(flag) { 345 a.Flags = append(a.Flags, flag) 346 } 347 } 348 349 // Author represents someone who has contributed to a cli project. 350 type Author struct { 351 Name string // The Authors name 352 Email string // The Authors email 353 } 354 355 // String makes Author comply to the Stringer interface, to allow an easy print in the templating process 356 func (a Author) String() string { 357 e := "" 358 if a.Email != "" { 359 e = "<" + a.Email + "> " 360 } 361 362 return fmt.Sprintf("%v %v", a.Name, e) 363 }