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