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