1package flags 2 3import ( 4 "reflect" 5 "sort" 6 "strconv" 7 "strings" 8 "unsafe" 9) 10 11// Command represents an application command. Commands can be added to the 12// parser (which itself is a command) and are selected/executed when its name 13// is specified on the command line. The Command type embeds a Group and 14// therefore also carries a set of command specific options. 15type Command struct { 16 // Embedded, see Group for more information 17 *Group 18 19 // The name by which the command can be invoked 20 Name string 21 22 // The active sub command (set by parsing) or nil 23 Active *Command 24 25 // Whether subcommands are optional 26 SubcommandsOptional bool 27 28 // Aliases for the command 29 Aliases []string 30 31 // Whether positional arguments are required 32 ArgsRequired bool 33 34 commands []*Command 35 hasBuiltinHelpGroup bool 36 args []*Arg 37} 38 39// Commander is an interface which can be implemented by any command added in 40// the options. When implemented, the Execute method will be called for the last 41// specified (sub)command providing the remaining command line arguments. 42type Commander interface { 43 // Execute will be called for the last active (sub)command. The 44 // args argument contains the remaining command line arguments. The 45 // error that Execute returns will be eventually passed out of the 46 // Parse method of the Parser. 47 Execute(args []string) error 48} 49 50// Usage is an interface which can be implemented to show a custom usage string 51// in the help message shown for a command. 52type Usage interface { 53 // Usage is called for commands to allow customized printing of command 54 // usage in the generated help message. 55 Usage() string 56} 57 58type lookup struct { 59 shortNames map[string]*Option 60 longNames map[string]*Option 61 62 commands map[string]*Command 63} 64 65// AddCommand adds a new command to the parser with the given name and data. The 66// data needs to be a pointer to a struct from which the fields indicate which 67// options are in the command. The provided data can implement the Command and 68// Usage interfaces. 69func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) { 70 cmd := newCommand(command, shortDescription, longDescription, data) 71 72 cmd.parent = c 73 74 if err := cmd.scan(); err != nil { 75 return nil, err 76 } 77 78 c.commands = append(c.commands, cmd) 79 return cmd, nil 80} 81 82// AddGroup adds a new group to the command with the given name and data. The 83// data needs to be a pointer to a struct from which the fields indicate which 84// options are in the group. 85func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) { 86 group := newGroup(shortDescription, longDescription, data) 87 88 group.parent = c 89 90 if err := group.scanType(c.scanSubcommandHandler(group)); err != nil { 91 return nil, err 92 } 93 94 c.groups = append(c.groups, group) 95 return group, nil 96} 97 98// Commands returns a list of subcommands of this command. 99func (c *Command) Commands() []*Command { 100 return c.commands 101} 102 103// Find locates the subcommand with the given name and returns it. If no such 104// command can be found Find will return nil. 105func (c *Command) Find(name string) *Command { 106 for _, cc := range c.commands { 107 if cc.match(name) { 108 return cc 109 } 110 } 111 112 return nil 113} 114 115// Find an option that is part of the command, or any of its 116// parent commands, by matching its long name 117// (including the option namespace). 118func (c *Command) FindOptionByLongName(longName string) (option *Option) { 119 for option == nil && c != nil { 120 option = c.Group.FindOptionByLongName(longName) 121 122 c, _ = c.parent.(*Command) 123 } 124 125 return option 126} 127 128// Find an option that is part of the command, or any of its 129// parent commands, by matching its long name 130// (including the option namespace). 131func (c *Command) FindOptionByShortName(shortName rune) (option *Option) { 132 for option == nil && c != nil { 133 option = c.Group.FindOptionByShortName(shortName) 134 135 c, _ = c.parent.(*Command) 136 } 137 138 return option 139} 140 141// Args returns a list of positional arguments associated with this command. 142func (c *Command) Args() []*Arg { 143 ret := make([]*Arg, len(c.args)) 144 copy(ret, c.args) 145 146 return ret 147} 148 149func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command { 150 return &Command{ 151 Group: newGroup(shortDescription, longDescription, data), 152 Name: name, 153 } 154} 155 156func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler { 157 f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) { 158 mtag := newMultiTag(string(sfield.Tag)) 159 160 if err := mtag.Parse(); err != nil { 161 return true, err 162 } 163 164 positional := mtag.Get("positional-args") 165 166 if len(positional) != 0 { 167 stype := realval.Type() 168 169 for i := 0; i < stype.NumField(); i++ { 170 field := stype.Field(i) 171 172 m := newMultiTag((string(field.Tag))) 173 174 if err := m.Parse(); err != nil { 175 return true, err 176 } 177 178 name := m.Get("positional-arg-name") 179 180 if len(name) == 0 { 181 name = field.Name 182 } 183 184 var required int 185 186 sreq := m.Get("required") 187 188 if sreq != "" { 189 required = 1 190 191 if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil { 192 required = int(preq) 193 } 194 } 195 196 arg := &Arg{ 197 Name: name, 198 Description: m.Get("description"), 199 Required: required, 200 201 value: realval.Field(i), 202 tag: m, 203 } 204 205 c.args = append(c.args, arg) 206 207 if len(mtag.Get("required")) != 0 { 208 c.ArgsRequired = true 209 } 210 } 211 212 return true, nil 213 } 214 215 subcommand := mtag.Get("command") 216 217 if len(subcommand) != 0 { 218 ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr())) 219 220 shortDescription := mtag.Get("description") 221 longDescription := mtag.Get("long-description") 222 subcommandsOptional := mtag.Get("subcommands-optional") 223 aliases := mtag.GetMany("alias") 224 225 subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface()) 226 if err != nil { 227 return true, err 228 } 229 230 subc.Hidden = mtag.Get("hidden") != "" 231 232 if len(subcommandsOptional) > 0 { 233 subc.SubcommandsOptional = true 234 } 235 236 if len(aliases) > 0 { 237 subc.Aliases = aliases 238 } 239 240 return true, nil 241 } 242 243 return parentg.scanSubGroupHandler(realval, sfield) 244 } 245 246 return f 247} 248 249func (c *Command) scan() error { 250 return c.scanType(c.scanSubcommandHandler(c.Group)) 251} 252 253func (c *Command) eachOption(f func(*Command, *Group, *Option)) { 254 c.eachCommand(func(c *Command) { 255 c.eachGroup(func(g *Group) { 256 for _, option := range g.options { 257 f(c, g, option) 258 } 259 }) 260 }, true) 261} 262 263func (c *Command) eachCommand(f func(*Command), recurse bool) { 264 f(c) 265 266 for _, cc := range c.commands { 267 if recurse { 268 cc.eachCommand(f, true) 269 } else { 270 f(cc) 271 } 272 } 273} 274 275func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) { 276 c.eachGroup(func(g *Group) { 277 f(c, g) 278 }) 279 280 if c.Active != nil { 281 c.Active.eachActiveGroup(f) 282 } 283} 284 285func (c *Command) addHelpGroups(showHelp func() error) { 286 if !c.hasBuiltinHelpGroup { 287 c.addHelpGroup(showHelp) 288 c.hasBuiltinHelpGroup = true 289 } 290 291 for _, cc := range c.commands { 292 cc.addHelpGroups(showHelp) 293 } 294} 295 296func (c *Command) makeLookup() lookup { 297 ret := lookup{ 298 shortNames: make(map[string]*Option), 299 longNames: make(map[string]*Option), 300 commands: make(map[string]*Command), 301 } 302 303 parent := c.parent 304 305 var parents []*Command 306 307 for parent != nil { 308 if cmd, ok := parent.(*Command); ok { 309 parents = append(parents, cmd) 310 parent = cmd.parent 311 } else { 312 parent = nil 313 } 314 } 315 316 for i := len(parents) - 1; i >= 0; i-- { 317 parents[i].fillLookup(&ret, true) 318 } 319 320 c.fillLookup(&ret, false) 321 return ret 322} 323 324func (c *Command) fillLookup(ret *lookup, onlyOptions bool) { 325 c.eachGroup(func(g *Group) { 326 for _, option := range g.options { 327 if option.ShortName != 0 { 328 ret.shortNames[string(option.ShortName)] = option 329 } 330 331 if len(option.LongName) > 0 { 332 ret.longNames[option.LongNameWithNamespace()] = option 333 } 334 } 335 }) 336 337 if onlyOptions { 338 return 339 } 340 341 for _, subcommand := range c.commands { 342 ret.commands[subcommand.Name] = subcommand 343 344 for _, a := range subcommand.Aliases { 345 ret.commands[a] = subcommand 346 } 347 } 348} 349 350func (c *Command) groupByName(name string) *Group { 351 if grp := c.Group.groupByName(name); grp != nil { 352 return grp 353 } 354 355 for _, subc := range c.commands { 356 prefix := subc.Name + "." 357 358 if strings.HasPrefix(name, prefix) { 359 if grp := subc.groupByName(name[len(prefix):]); grp != nil { 360 return grp 361 } 362 } else if name == subc.Name { 363 return subc.Group 364 } 365 } 366 367 return nil 368} 369 370type commandList []*Command 371 372func (c commandList) Less(i, j int) bool { 373 return c[i].Name < c[j].Name 374} 375 376func (c commandList) Len() int { 377 return len(c) 378} 379 380func (c commandList) Swap(i, j int) { 381 c[i], c[j] = c[j], c[i] 382} 383 384func (c *Command) sortedVisibleCommands() []*Command { 385 ret := commandList(c.visibleCommands()) 386 sort.Sort(ret) 387 388 return []*Command(ret) 389} 390 391func (c *Command) visibleCommands() []*Command { 392 ret := make([]*Command, 0, len(c.commands)) 393 394 for _, cmd := range c.commands { 395 if !cmd.Hidden { 396 ret = append(ret, cmd) 397 } 398 } 399 400 return ret 401} 402 403func (c *Command) match(name string) bool { 404 if c.Name == name { 405 return true 406 } 407 408 for _, v := range c.Aliases { 409 if v == name { 410 return true 411 } 412 } 413 414 return false 415} 416 417func (c *Command) hasCliOptions() bool { 418 ret := false 419 420 c.eachGroup(func(g *Group) { 421 if g.isBuiltinHelp { 422 return 423 } 424 425 for _, opt := range g.options { 426 if opt.canCli() { 427 ret = true 428 } 429 } 430 }) 431 432 return ret 433} 434 435func (c *Command) fillParseState(s *parseState) { 436 s.positional = make([]*Arg, len(c.args)) 437 copy(s.positional, c.args) 438 439 s.lookup = c.makeLookup() 440 s.command = c 441} 442