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// FindOptionByLongName finds an option that is part of the command, or any of 116// its parent commands, by matching its long name (including the option 117// 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// FindOptionByShortName finds an option that is part of the command, or any of 129// its parent commands, by matching its long name (including the option 130// 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 required := -1 185 requiredMaximum := -1 186 187 sreq := m.Get("required") 188 189 if sreq != "" { 190 required = 1 191 192 rng := strings.SplitN(sreq, "-", 2) 193 194 if len(rng) > 1 { 195 if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil { 196 required = int(preq) 197 } 198 199 if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil { 200 requiredMaximum = int(preq) 201 } 202 } else { 203 if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil { 204 required = int(preq) 205 } 206 } 207 } 208 209 arg := &Arg{ 210 Name: name, 211 Description: m.Get("description"), 212 Required: required, 213 RequiredMaximum: requiredMaximum, 214 215 value: realval.Field(i), 216 tag: m, 217 } 218 219 c.args = append(c.args, arg) 220 221 if len(mtag.Get("required")) != 0 { 222 c.ArgsRequired = true 223 } 224 } 225 226 return true, nil 227 } 228 229 subcommand := mtag.Get("command") 230 231 if len(subcommand) != 0 { 232 ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr())) 233 234 shortDescription := mtag.Get("description") 235 longDescription := mtag.Get("long-description") 236 subcommandsOptional := mtag.Get("subcommands-optional") 237 aliases := mtag.GetMany("alias") 238 239 subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface()) 240 if err != nil { 241 return true, err 242 } 243 244 subc.Hidden = mtag.Get("hidden") != "" 245 246 if len(subcommandsOptional) > 0 { 247 subc.SubcommandsOptional = true 248 } 249 250 if len(aliases) > 0 { 251 subc.Aliases = aliases 252 } 253 254 return true, nil 255 } 256 257 return parentg.scanSubGroupHandler(realval, sfield) 258 } 259 260 return f 261} 262 263func (c *Command) scan() error { 264 return c.scanType(c.scanSubcommandHandler(c.Group)) 265} 266 267func (c *Command) eachOption(f func(*Command, *Group, *Option)) { 268 c.eachCommand(func(c *Command) { 269 c.eachGroup(func(g *Group) { 270 for _, option := range g.options { 271 f(c, g, option) 272 } 273 }) 274 }, true) 275} 276 277func (c *Command) eachCommand(f func(*Command), recurse bool) { 278 f(c) 279 280 for _, cc := range c.commands { 281 if recurse { 282 cc.eachCommand(f, true) 283 } else { 284 f(cc) 285 } 286 } 287} 288 289func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) { 290 c.eachGroup(func(g *Group) { 291 f(c, g) 292 }) 293 294 if c.Active != nil { 295 c.Active.eachActiveGroup(f) 296 } 297} 298 299func (c *Command) addHelpGroups(showHelp func() error) { 300 if !c.hasBuiltinHelpGroup { 301 c.addHelpGroup(showHelp) 302 c.hasBuiltinHelpGroup = true 303 } 304 305 for _, cc := range c.commands { 306 cc.addHelpGroups(showHelp) 307 } 308} 309 310func (c *Command) makeLookup() lookup { 311 ret := lookup{ 312 shortNames: make(map[string]*Option), 313 longNames: make(map[string]*Option), 314 commands: make(map[string]*Command), 315 } 316 317 parent := c.parent 318 319 var parents []*Command 320 321 for parent != nil { 322 if cmd, ok := parent.(*Command); ok { 323 parents = append(parents, cmd) 324 parent = cmd.parent 325 } else { 326 parent = nil 327 } 328 } 329 330 for i := len(parents) - 1; i >= 0; i-- { 331 parents[i].fillLookup(&ret, true) 332 } 333 334 c.fillLookup(&ret, false) 335 return ret 336} 337 338func (c *Command) fillLookup(ret *lookup, onlyOptions bool) { 339 c.eachGroup(func(g *Group) { 340 for _, option := range g.options { 341 if option.ShortName != 0 { 342 ret.shortNames[string(option.ShortName)] = option 343 } 344 345 if len(option.LongName) > 0 { 346 ret.longNames[option.LongNameWithNamespace()] = option 347 } 348 } 349 }) 350 351 if onlyOptions { 352 return 353 } 354 355 for _, subcommand := range c.commands { 356 ret.commands[subcommand.Name] = subcommand 357 358 for _, a := range subcommand.Aliases { 359 ret.commands[a] = subcommand 360 } 361 } 362} 363 364func (c *Command) groupByName(name string) *Group { 365 if grp := c.Group.groupByName(name); grp != nil { 366 return grp 367 } 368 369 for _, subc := range c.commands { 370 prefix := subc.Name + "." 371 372 if strings.HasPrefix(name, prefix) { 373 if grp := subc.groupByName(name[len(prefix):]); grp != nil { 374 return grp 375 } 376 } else if name == subc.Name { 377 return subc.Group 378 } 379 } 380 381 return nil 382} 383 384type commandList []*Command 385 386func (c commandList) Less(i, j int) bool { 387 return c[i].Name < c[j].Name 388} 389 390func (c commandList) Len() int { 391 return len(c) 392} 393 394func (c commandList) Swap(i, j int) { 395 c[i], c[j] = c[j], c[i] 396} 397 398func (c *Command) sortedVisibleCommands() []*Command { 399 ret := commandList(c.visibleCommands()) 400 sort.Sort(ret) 401 402 return []*Command(ret) 403} 404 405func (c *Command) visibleCommands() []*Command { 406 ret := make([]*Command, 0, len(c.commands)) 407 408 for _, cmd := range c.commands { 409 if !cmd.Hidden { 410 ret = append(ret, cmd) 411 } 412 } 413 414 return ret 415} 416 417func (c *Command) match(name string) bool { 418 if c.Name == name { 419 return true 420 } 421 422 for _, v := range c.Aliases { 423 if v == name { 424 return true 425 } 426 } 427 428 return false 429} 430 431func (c *Command) hasCliOptions() bool { 432 ret := false 433 434 c.eachGroup(func(g *Group) { 435 if g.isBuiltinHelp { 436 return 437 } 438 439 for _, opt := range g.options { 440 if opt.canCli() { 441 ret = true 442 } 443 } 444 }) 445 446 return ret 447} 448 449func (c *Command) fillParseState(s *parseState) { 450 s.positional = make([]*Arg, len(c.args)) 451 copy(s.positional, c.args) 452 453 s.lookup = c.makeLookup() 454 s.command = c 455} 456