1// Copyright 2012 Jesse van den Kieboom. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package flags 6 7import ( 8 "bytes" 9 "fmt" 10 "os" 11 "path" 12 "sort" 13 "strings" 14 "unicode/utf8" 15) 16 17// A Parser provides command line option parsing. It can contain several 18// option groups each with their own set of options. 19type Parser struct { 20 // Embedded, see Command for more information 21 *Command 22 23 // A usage string to be displayed in the help message. 24 Usage string 25 26 // Option flags changing the behavior of the parser. 27 Options Options 28 29 // NamespaceDelimiter separates group namespaces and option long names 30 NamespaceDelimiter string 31 32 // UnknownOptionsHandler is a function which gets called when the parser 33 // encounters an unknown option. The function receives the unknown option 34 // name, a SplitArgument which specifies its value if set with an argument 35 // separator, and the remaining command line arguments. 36 // It should return a new list of remaining arguments to continue parsing, 37 // or an error to indicate a parse failure. 38 UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error) 39 40 // CompletionHandler is a function gets called to handle the completion of 41 // items. By default, the items are printed and the application is exited. 42 // You can override this default behavior by specifying a custom CompletionHandler. 43 CompletionHandler func(items []Completion) 44 45 // CommandHandler is a function that gets called to handle execution of a 46 // command. By default, the command will simply be executed. This can be 47 // overridden to perform certain actions (such as applying global flags) 48 // just before the command is executed. Note that if you override the 49 // handler it is your responsibility to call the command.Execute function. 50 // 51 // The command passed into CommandHandler may be nil in case there is no 52 // command to be executed when parsing has finished. 53 CommandHandler func(command Commander, args []string) error 54 55 internalError error 56} 57 58// SplitArgument represents the argument value of an option that was passed using 59// an argument separator. 60type SplitArgument interface { 61 // String returns the option's value as a string, and a boolean indicating 62 // if the option was present. 63 Value() (string, bool) 64} 65 66type strArgument struct { 67 value *string 68} 69 70func (s strArgument) Value() (string, bool) { 71 if s.value == nil { 72 return "", false 73 } 74 75 return *s.value, true 76} 77 78// Options provides parser options that change the behavior of the option 79// parser. 80type Options uint 81 82const ( 83 // None indicates no options. 84 None Options = 0 85 86 // HelpFlag adds a default Help Options group to the parser containing 87 // -h and --help options. When either -h or --help is specified on the 88 // command line, the parser will return the special error of type 89 // ErrHelp. When PrintErrors is also specified, then the help message 90 // will also be automatically printed to os.Stdout. 91 HelpFlag = 1 << iota 92 93 // PassDoubleDash passes all arguments after a double dash, --, as 94 // remaining command line arguments (i.e. they will not be parsed for 95 // flags). 96 PassDoubleDash 97 98 // IgnoreUnknown ignores any unknown options and passes them as 99 // remaining command line arguments instead of generating an error. 100 IgnoreUnknown 101 102 // PrintErrors prints any errors which occurred during parsing to 103 // os.Stderr. In the special case of ErrHelp, the message will be printed 104 // to os.Stdout. 105 PrintErrors 106 107 // PassAfterNonOption passes all arguments after the first non option 108 // as remaining command line arguments. This is equivalent to strict 109 // POSIX processing. 110 PassAfterNonOption 111 112 // Default is a convenient default set of options which should cover 113 // most of the uses of the flags package. 114 Default = HelpFlag | PrintErrors | PassDoubleDash 115) 116 117type parseState struct { 118 arg string 119 args []string 120 retargs []string 121 positional []*Arg 122 err error 123 124 command *Command 125 lookup lookup 126} 127 128// Parse is a convenience function to parse command line options with default 129// settings. The provided data is a pointer to a struct representing the 130// default option group (named "Application Options"). For more control, use 131// flags.NewParser. 132func Parse(data interface{}) ([]string, error) { 133 return NewParser(data, Default).Parse() 134} 135 136// ParseArgs is a convenience function to parse command line options with default 137// settings. The provided data is a pointer to a struct representing the 138// default option group (named "Application Options"). The args argument is 139// the list of command line arguments to parse. If you just want to parse the 140// default program command line arguments (i.e. os.Args), then use flags.Parse 141// instead. For more control, use flags.NewParser. 142func ParseArgs(data interface{}, args []string) ([]string, error) { 143 return NewParser(data, Default).ParseArgs(args) 144} 145 146// NewParser creates a new parser. It uses os.Args[0] as the application 147// name and then calls Parser.NewNamedParser (see Parser.NewNamedParser for 148// more details). The provided data is a pointer to a struct representing the 149// default option group (named "Application Options"), or nil if the default 150// group should not be added. The options parameter specifies a set of options 151// for the parser. 152func NewParser(data interface{}, options Options) *Parser { 153 p := NewNamedParser(path.Base(os.Args[0]), options) 154 155 if data != nil { 156 g, err := p.AddGroup("Application Options", "", data) 157 158 if err == nil { 159 g.parent = p 160 } 161 162 p.internalError = err 163 } 164 165 return p 166} 167 168// NewNamedParser creates a new parser. The appname is used to display the 169// executable name in the built-in help message. Option groups and commands can 170// be added to this parser by using AddGroup and AddCommand. 171func NewNamedParser(appname string, options Options) *Parser { 172 p := &Parser{ 173 Command: newCommand(appname, "", "", nil), 174 Options: options, 175 NamespaceDelimiter: ".", 176 } 177 178 p.Command.parent = p 179 180 return p 181} 182 183// Parse parses the command line arguments from os.Args using Parser.ParseArgs. 184// For more detailed information see ParseArgs. 185func (p *Parser) Parse() ([]string, error) { 186 return p.ParseArgs(os.Args[1:]) 187} 188 189// ParseArgs parses the command line arguments according to the option groups that 190// were added to the parser. On successful parsing of the arguments, the 191// remaining, non-option, arguments (if any) are returned. The returned error 192// indicates a parsing error and can be used with PrintError to display 193// contextual information on where the error occurred exactly. 194// 195// When the common help group has been added (AddHelp) and either -h or --help 196// was specified in the command line arguments, a help message will be 197// automatically printed if the PrintErrors option is enabled. 198// Furthermore, the special error type ErrHelp is returned. 199// It is up to the caller to exit the program if so desired. 200func (p *Parser) ParseArgs(args []string) ([]string, error) { 201 if p.internalError != nil { 202 return nil, p.internalError 203 } 204 205 p.eachOption(func(c *Command, g *Group, option *Option) { 206 option.isSet = false 207 option.isSetDefault = false 208 option.updateDefaultLiteral() 209 }) 210 211 // Add built-in help group to all commands if necessary 212 if (p.Options & HelpFlag) != None { 213 p.addHelpGroups(p.showBuiltinHelp) 214 } 215 216 compval := os.Getenv("GO_FLAGS_COMPLETION") 217 218 if len(compval) != 0 { 219 comp := &completion{parser: p} 220 items := comp.complete(args) 221 222 if p.CompletionHandler != nil { 223 p.CompletionHandler(items) 224 } else { 225 comp.print(items, compval == "verbose") 226 os.Exit(0) 227 } 228 229 return nil, nil 230 } 231 232 s := &parseState{ 233 args: args, 234 retargs: make([]string, 0, len(args)), 235 } 236 237 p.fillParseState(s) 238 239 for !s.eof() { 240 arg := s.pop() 241 242 // When PassDoubleDash is set and we encounter a --, then 243 // simply append all the rest as arguments and break out 244 if (p.Options&PassDoubleDash) != None && arg == "--" { 245 s.addArgs(s.args...) 246 break 247 } 248 249 if !argumentIsOption(arg) { 250 // Note: this also sets s.err, so we can just check for 251 // nil here and use s.err later 252 if p.parseNonOption(s) != nil { 253 break 254 } 255 256 continue 257 } 258 259 var err error 260 261 prefix, optname, islong := stripOptionPrefix(arg) 262 optname, _, argument := splitOption(prefix, optname, islong) 263 264 if islong { 265 err = p.parseLong(s, optname, argument) 266 } else { 267 err = p.parseShort(s, optname, argument) 268 } 269 270 if err != nil { 271 ignoreUnknown := (p.Options & IgnoreUnknown) != None 272 parseErr := wrapError(err) 273 274 if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) { 275 s.err = parseErr 276 break 277 } 278 279 if ignoreUnknown { 280 s.addArgs(arg) 281 } else if p.UnknownOptionHandler != nil { 282 modifiedArgs, err := p.UnknownOptionHandler(optname, strArgument{argument}, s.args) 283 284 if err != nil { 285 s.err = err 286 break 287 } 288 289 s.args = modifiedArgs 290 } 291 } 292 } 293 294 if s.err == nil { 295 p.eachOption(func(c *Command, g *Group, option *Option) { 296 if option.preventDefault { 297 return 298 } 299 300 option.clearDefault() 301 }) 302 303 s.checkRequired(p) 304 } 305 306 var reterr error 307 308 if s.err != nil { 309 reterr = s.err 310 } else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional { 311 reterr = s.estimateCommand() 312 } else if cmd, ok := s.command.data.(Commander); ok { 313 if p.CommandHandler != nil { 314 reterr = p.CommandHandler(cmd, s.retargs) 315 } else { 316 reterr = cmd.Execute(s.retargs) 317 } 318 } else if p.CommandHandler != nil { 319 reterr = p.CommandHandler(nil, s.retargs) 320 } 321 322 if reterr != nil { 323 var retargs []string 324 325 if ourErr, ok := reterr.(*Error); !ok || ourErr.Type != ErrHelp { 326 retargs = append([]string{s.arg}, s.args...) 327 } else { 328 retargs = s.args 329 } 330 331 return retargs, p.printError(reterr) 332 } 333 334 return s.retargs, nil 335} 336 337func (p *parseState) eof() bool { 338 return len(p.args) == 0 339} 340 341func (p *parseState) pop() string { 342 if p.eof() { 343 return "" 344 } 345 346 p.arg = p.args[0] 347 p.args = p.args[1:] 348 349 return p.arg 350} 351 352func (p *parseState) peek() string { 353 if p.eof() { 354 return "" 355 } 356 357 return p.args[0] 358} 359 360func (p *parseState) checkRequired(parser *Parser) error { 361 c := parser.Command 362 363 var required []*Option 364 365 for c != nil { 366 c.eachGroup(func(g *Group) { 367 for _, option := range g.options { 368 if !option.isSet && option.Required { 369 required = append(required, option) 370 } 371 } 372 }) 373 374 c = c.Active 375 } 376 377 if len(required) == 0 { 378 if len(p.positional) > 0 { 379 var reqnames []string 380 381 for _, arg := range p.positional { 382 argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1 383 384 if !argRequired { 385 continue 386 } 387 388 if arg.isRemaining() { 389 if arg.value.Len() < arg.Required { 390 var arguments string 391 392 if arg.Required > 1 { 393 arguments = "arguments, but got only " + fmt.Sprintf("%d", arg.value.Len()) 394 } else { 395 arguments = "argument" 396 } 397 398 reqnames = append(reqnames, "`"+arg.Name+" (at least "+fmt.Sprintf("%d", arg.Required)+" "+arguments+")`") 399 } else if arg.RequiredMaximum != -1 && arg.value.Len() > arg.RequiredMaximum { 400 if arg.RequiredMaximum == 0 { 401 reqnames = append(reqnames, "`"+arg.Name+" (zero arguments)`") 402 } else { 403 var arguments string 404 405 if arg.RequiredMaximum > 1 { 406 arguments = "arguments, but got " + fmt.Sprintf("%d", arg.value.Len()) 407 } else { 408 arguments = "argument" 409 } 410 411 reqnames = append(reqnames, "`"+arg.Name+" (at most "+fmt.Sprintf("%d", arg.RequiredMaximum)+" "+arguments+")`") 412 } 413 } 414 } else { 415 reqnames = append(reqnames, "`"+arg.Name+"`") 416 } 417 } 418 419 if len(reqnames) == 0 { 420 return nil 421 } 422 423 var msg string 424 425 if len(reqnames) == 1 { 426 msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0]) 427 } else { 428 msg = fmt.Sprintf("the required arguments %s and %s were not provided", 429 strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1]) 430 } 431 432 p.err = newError(ErrRequired, msg) 433 return p.err 434 } 435 436 return nil 437 } 438 439 names := make([]string, 0, len(required)) 440 441 for _, k := range required { 442 names = append(names, "`"+k.String()+"'") 443 } 444 445 sort.Strings(names) 446 447 var msg string 448 449 if len(names) == 1 { 450 msg = fmt.Sprintf("the required flag %s was not specified", names[0]) 451 } else { 452 msg = fmt.Sprintf("the required flags %s and %s were not specified", 453 strings.Join(names[:len(names)-1], ", "), names[len(names)-1]) 454 } 455 456 p.err = newError(ErrRequired, msg) 457 return p.err 458} 459 460func (p *parseState) estimateCommand() error { 461 commands := p.command.sortedVisibleCommands() 462 cmdnames := make([]string, len(commands)) 463 464 for i, v := range commands { 465 cmdnames[i] = v.Name 466 } 467 468 var msg string 469 var errtype ErrorType 470 471 if len(p.retargs) != 0 { 472 c, l := closestChoice(p.retargs[0], cmdnames) 473 msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0]) 474 errtype = ErrUnknownCommand 475 476 if float32(l)/float32(len(c)) < 0.5 { 477 msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c) 478 } else if len(cmdnames) == 1 { 479 msg = fmt.Sprintf("%s. You should use the %s command", 480 msg, 481 cmdnames[0]) 482 } else if len(cmdnames) > 1 { 483 msg = fmt.Sprintf("%s. Please specify one command of: %s or %s", 484 msg, 485 strings.Join(cmdnames[:len(cmdnames)-1], ", "), 486 cmdnames[len(cmdnames)-1]) 487 } 488 } else { 489 errtype = ErrCommandRequired 490 491 if len(cmdnames) == 1 { 492 msg = fmt.Sprintf("Please specify the %s command", cmdnames[0]) 493 } else if len(cmdnames) > 1 { 494 msg = fmt.Sprintf("Please specify one command of: %s or %s", 495 strings.Join(cmdnames[:len(cmdnames)-1], ", "), 496 cmdnames[len(cmdnames)-1]) 497 } 498 } 499 500 return newError(errtype, msg) 501} 502 503func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) { 504 if !option.canArgument() { 505 if argument != nil { 506 return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option) 507 } 508 509 err = option.set(nil) 510 } else if argument != nil || (canarg && !s.eof()) { 511 var arg string 512 513 if argument != nil { 514 arg = *argument 515 } else { 516 arg = s.pop() 517 518 if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') { 519 return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg) 520 } else if p.Options&PassDoubleDash != 0 && arg == "--" { 521 return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option) 522 } 523 } 524 525 if option.tag.Get("unquote") != "false" { 526 arg, err = unquoteIfPossible(arg) 527 } 528 529 if err == nil { 530 err = option.set(&arg) 531 } 532 } else if option.OptionalArgument { 533 option.empty() 534 535 for _, v := range option.OptionalValue { 536 err = option.set(&v) 537 538 if err != nil { 539 break 540 } 541 } 542 } else { 543 err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option) 544 } 545 546 if err != nil { 547 if _, ok := err.(*Error); !ok { 548 err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s", 549 option, 550 option.value.Type(), 551 err.Error()) 552 } 553 } 554 555 return err 556} 557 558func (p *Parser) parseLong(s *parseState, name string, argument *string) error { 559 if option := s.lookup.longNames[name]; option != nil { 560 // Only long options that are required can consume an argument 561 // from the argument list 562 canarg := !option.OptionalArgument 563 564 return p.parseOption(s, name, option, canarg, argument) 565 } 566 567 return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name) 568} 569 570func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) { 571 c, n := utf8.DecodeRuneInString(optname) 572 573 if n == len(optname) { 574 return optname, nil 575 } 576 577 first := string(c) 578 579 if option := s.lookup.shortNames[first]; option != nil && option.canArgument() { 580 arg := optname[n:] 581 return first, &arg 582 } 583 584 return optname, nil 585} 586 587func (p *Parser) parseShort(s *parseState, optname string, argument *string) error { 588 if argument == nil { 589 optname, argument = p.splitShortConcatArg(s, optname) 590 } 591 592 for i, c := range optname { 593 shortname := string(c) 594 595 if option := s.lookup.shortNames[shortname]; option != nil { 596 // Only the last short argument can consume an argument from 597 // the arguments list, and only if it's non optional 598 canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument 599 600 if err := p.parseOption(s, shortname, option, canarg, argument); err != nil { 601 return err 602 } 603 } else { 604 return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname) 605 } 606 607 // Only the first option can have a concatted argument, so just 608 // clear argument here 609 argument = nil 610 } 611 612 return nil 613} 614 615func (p *parseState) addArgs(args ...string) error { 616 for len(p.positional) > 0 && len(args) > 0 { 617 arg := p.positional[0] 618 619 if err := convert(args[0], arg.value, arg.tag); err != nil { 620 p.err = err 621 return err 622 } 623 624 if !arg.isRemaining() { 625 p.positional = p.positional[1:] 626 } 627 628 args = args[1:] 629 } 630 631 p.retargs = append(p.retargs, args...) 632 return nil 633} 634 635func (p *Parser) parseNonOption(s *parseState) error { 636 if len(s.positional) > 0 { 637 return s.addArgs(s.arg) 638 } 639 640 if len(s.command.commands) > 0 && len(s.retargs) == 0 { 641 if cmd := s.lookup.commands[s.arg]; cmd != nil { 642 s.command.Active = cmd 643 cmd.fillParseState(s) 644 645 return nil 646 } else if !s.command.SubcommandsOptional { 647 s.addArgs(s.arg) 648 return newErrorf(ErrUnknownCommand, "Unknown command `%s'", s.arg) 649 } 650 } 651 652 if (p.Options & PassAfterNonOption) != None { 653 // If PassAfterNonOption is set then all remaining arguments 654 // are considered positional 655 if err := s.addArgs(s.arg); err != nil { 656 return err 657 } 658 659 if err := s.addArgs(s.args...); err != nil { 660 return err 661 } 662 663 s.args = []string{} 664 } else { 665 return s.addArgs(s.arg) 666 } 667 668 return nil 669} 670 671func (p *Parser) showBuiltinHelp() error { 672 var b bytes.Buffer 673 674 p.WriteHelp(&b) 675 return newError(ErrHelp, b.String()) 676} 677 678func (p *Parser) printError(err error) error { 679 if err != nil && (p.Options&PrintErrors) != None { 680 flagsErr, ok := err.(*Error) 681 682 if ok && flagsErr.Type == ErrHelp { 683 fmt.Fprintln(os.Stdout, err) 684 } else { 685 fmt.Fprintln(os.Stderr, err) 686 } 687 } 688 689 return err 690} 691 692func (p *Parser) clearIsSet() { 693 p.eachCommand(func(c *Command) { 694 c.eachGroup(func(g *Group) { 695 for _, option := range g.options { 696 option.isSet = false 697 } 698 }) 699 }, true) 700} 701