// Copyright 2012 Jesse van den Kieboom. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package flags import ( "bytes" "fmt" "os" "path" "sort" "strings" "unicode/utf8" ) // A Parser provides command line option parsing. It can contain several // option groups each with their own set of options. type Parser struct { // Embedded, see Command for more information *Command // A usage string to be displayed in the help message. Usage string // Option flags changing the behavior of the parser. Options Options // NamespaceDelimiter separates group namespaces and option long names NamespaceDelimiter string // UnknownOptionsHandler is a function which gets called when the parser // encounters an unknown option. The function receives the unknown option // name, a SplitArgument which specifies its value if set with an argument // separator, and the remaining command line arguments. // It should return a new list of remaining arguments to continue parsing, // or an error to indicate a parse failure. UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error) // CompletionHandler is a function gets called to handle the completion of // items. By default, the items are printed and the application is exited. // You can override this default behavior by specifying a custom CompletionHandler. CompletionHandler func(items []Completion) // CommandHandler is a function that gets called to handle execution of a // command. By default, the command will simply be executed. This can be // overridden to perform certain actions (such as applying global flags) // just before the command is executed. Note that if you override the // handler it is your responsibility to call the command.Execute function. // // The command passed into CommandHandler may be nil in case there is no // command to be executed when parsing has finished. CommandHandler func(command Commander, args []string) error internalError error } // SplitArgument represents the argument value of an option that was passed using // an argument separator. type SplitArgument interface { // String returns the option's value as a string, and a boolean indicating // if the option was present. Value() (string, bool) } type strArgument struct { value *string } func (s strArgument) Value() (string, bool) { if s.value == nil { return "", false } return *s.value, true } // Options provides parser options that change the behavior of the option // parser. type Options uint const ( // None indicates no options. None Options = 0 // HelpFlag adds a default Help Options group to the parser containing // -h and --help options. When either -h or --help is specified on the // command line, the parser will return the special error of type // ErrHelp. When PrintErrors is also specified, then the help message // will also be automatically printed to os.Stdout. HelpFlag = 1 << iota // PassDoubleDash passes all arguments after a double dash, --, as // remaining command line arguments (i.e. they will not be parsed for // flags). PassDoubleDash // IgnoreUnknown ignores any unknown options and passes them as // remaining command line arguments instead of generating an error. IgnoreUnknown // PrintErrors prints any errors which occurred during parsing to // os.Stderr. In the special case of ErrHelp, the message will be printed // to os.Stdout. PrintErrors // PassAfterNonOption passes all arguments after the first non option // as remaining command line arguments. This is equivalent to strict // POSIX processing. PassAfterNonOption // Default is a convenient default set of options which should cover // most of the uses of the flags package. Default = HelpFlag | PrintErrors | PassDoubleDash ) type parseState struct { arg string args []string retargs []string positional []*Arg err error command *Command lookup lookup } // Parse is a convenience function to parse command line options with default // settings. The provided data is a pointer to a struct representing the // default option group (named "Application Options"). For more control, use // flags.NewParser. func Parse(data interface{}) ([]string, error) { return NewParser(data, Default).Parse() } // ParseArgs is a convenience function to parse command line options with default // settings. The provided data is a pointer to a struct representing the // default option group (named "Application Options"). The args argument is // the list of command line arguments to parse. If you just want to parse the // default program command line arguments (i.e. os.Args), then use flags.Parse // instead. For more control, use flags.NewParser. func ParseArgs(data interface{}, args []string) ([]string, error) { return NewParser(data, Default).ParseArgs(args) } // NewParser creates a new parser. It uses os.Args[0] as the application // name and then calls Parser.NewNamedParser (see Parser.NewNamedParser for // more details). The provided data is a pointer to a struct representing the // default option group (named "Application Options"), or nil if the default // group should not be added. The options parameter specifies a set of options // for the parser. func NewParser(data interface{}, options Options) *Parser { p := NewNamedParser(path.Base(os.Args[0]), options) if data != nil { g, err := p.AddGroup("Application Options", "", data) if err == nil { g.parent = p } p.internalError = err } return p } // NewNamedParser creates a new parser. The appname is used to display the // executable name in the built-in help message. Option groups and commands can // be added to this parser by using AddGroup and AddCommand. func NewNamedParser(appname string, options Options) *Parser { p := &Parser{ Command: newCommand(appname, "", "", nil), Options: options, NamespaceDelimiter: ".", } p.Command.parent = p return p } // Parse parses the command line arguments from os.Args using Parser.ParseArgs. // For more detailed information see ParseArgs. func (p *Parser) Parse() ([]string, error) { return p.ParseArgs(os.Args[1:]) } // ParseArgs parses the command line arguments according to the option groups that // were added to the parser. On successful parsing of the arguments, the // remaining, non-option, arguments (if any) are returned. The returned error // indicates a parsing error and can be used with PrintError to display // contextual information on where the error occurred exactly. // // When the common help group has been added (AddHelp) and either -h or --help // was specified in the command line arguments, a help message will be // automatically printed if the PrintErrors option is enabled. // Furthermore, the special error type ErrHelp is returned. // It is up to the caller to exit the program if so desired. func (p *Parser) ParseArgs(args []string) ([]string, error) { if p.internalError != nil { return nil, p.internalError } p.eachOption(func(c *Command, g *Group, option *Option) { option.isSet = false option.isSetDefault = false option.updateDefaultLiteral() }) // Add built-in help group to all commands if necessary if (p.Options & HelpFlag) != None { p.addHelpGroups(p.showBuiltinHelp) } compval := os.Getenv("GO_FLAGS_COMPLETION") if len(compval) != 0 { comp := &completion{parser: p} items := comp.complete(args) if p.CompletionHandler != nil { p.CompletionHandler(items) } else { comp.print(items, compval == "verbose") os.Exit(0) } return nil, nil } s := &parseState{ args: args, retargs: make([]string, 0, len(args)), } p.fillParseState(s) for !s.eof() { arg := s.pop() // When PassDoubleDash is set and we encounter a --, then // simply append all the rest as arguments and break out if (p.Options&PassDoubleDash) != None && arg == "--" { s.addArgs(s.args...) break } if !argumentIsOption(arg) { // Note: this also sets s.err, so we can just check for // nil here and use s.err later if p.parseNonOption(s) != nil { break } continue } var err error prefix, optname, islong := stripOptionPrefix(arg) optname, _, argument := splitOption(prefix, optname, islong) if islong { err = p.parseLong(s, optname, argument) } else { err = p.parseShort(s, optname, argument) } if err != nil { ignoreUnknown := (p.Options & IgnoreUnknown) != None parseErr := wrapError(err) if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) { s.err = parseErr break } if ignoreUnknown { s.addArgs(arg) } else if p.UnknownOptionHandler != nil { modifiedArgs, err := p.UnknownOptionHandler(optname, strArgument{argument}, s.args) if err != nil { s.err = err break } s.args = modifiedArgs } } } if s.err == nil { p.eachOption(func(c *Command, g *Group, option *Option) { if option.preventDefault { return } option.clearDefault() }) s.checkRequired(p) } var reterr error if s.err != nil { reterr = s.err } else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional { reterr = s.estimateCommand() } else if cmd, ok := s.command.data.(Commander); ok { if p.CommandHandler != nil { reterr = p.CommandHandler(cmd, s.retargs) } else { reterr = cmd.Execute(s.retargs) } } else if p.CommandHandler != nil { reterr = p.CommandHandler(nil, s.retargs) } if reterr != nil { var retargs []string if ourErr, ok := reterr.(*Error); !ok || ourErr.Type != ErrHelp { retargs = append([]string{s.arg}, s.args...) } else { retargs = s.args } return retargs, p.printError(reterr) } return s.retargs, nil } func (p *parseState) eof() bool { return len(p.args) == 0 } func (p *parseState) pop() string { if p.eof() { return "" } p.arg = p.args[0] p.args = p.args[1:] return p.arg } func (p *parseState) peek() string { if p.eof() { return "" } return p.args[0] } func (p *parseState) checkRequired(parser *Parser) error { c := parser.Command var required []*Option for c != nil { c.eachGroup(func(g *Group) { for _, option := range g.options { if !option.isSet && option.Required { required = append(required, option) } } }) c = c.Active } if len(required) == 0 { if len(p.positional) > 0 { var reqnames []string for _, arg := range p.positional { argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1 if !argRequired { continue } if arg.isRemaining() { if arg.value.Len() < arg.Required { var arguments string if arg.Required > 1 { arguments = "arguments, but got only " + fmt.Sprintf("%d", arg.value.Len()) } else { arguments = "argument" } reqnames = append(reqnames, "`"+arg.Name+" (at least "+fmt.Sprintf("%d", arg.Required)+" "+arguments+")`") } else if arg.RequiredMaximum != -1 && arg.value.Len() > arg.RequiredMaximum { if arg.RequiredMaximum == 0 { reqnames = append(reqnames, "`"+arg.Name+" (zero arguments)`") } else { var arguments string if arg.RequiredMaximum > 1 { arguments = "arguments, but got " + fmt.Sprintf("%d", arg.value.Len()) } else { arguments = "argument" } reqnames = append(reqnames, "`"+arg.Name+" (at most "+fmt.Sprintf("%d", arg.RequiredMaximum)+" "+arguments+")`") } } } else { reqnames = append(reqnames, "`"+arg.Name+"`") } } if len(reqnames) == 0 { return nil } var msg string if len(reqnames) == 1 { msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0]) } else { msg = fmt.Sprintf("the required arguments %s and %s were not provided", strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1]) } p.err = newError(ErrRequired, msg) return p.err } return nil } names := make([]string, 0, len(required)) for _, k := range required { names = append(names, "`"+k.String()+"'") } sort.Strings(names) var msg string if len(names) == 1 { msg = fmt.Sprintf("the required flag %s was not specified", names[0]) } else { msg = fmt.Sprintf("the required flags %s and %s were not specified", strings.Join(names[:len(names)-1], ", "), names[len(names)-1]) } p.err = newError(ErrRequired, msg) return p.err } func (p *parseState) estimateCommand() error { commands := p.command.sortedVisibleCommands() cmdnames := make([]string, len(commands)) for i, v := range commands { cmdnames[i] = v.Name } var msg string var errtype ErrorType if len(p.retargs) != 0 { c, l := closestChoice(p.retargs[0], cmdnames) msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0]) errtype = ErrUnknownCommand if float32(l)/float32(len(c)) < 0.5 { msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c) } else if len(cmdnames) == 1 { msg = fmt.Sprintf("%s. You should use the %s command", msg, cmdnames[0]) } else if len(cmdnames) > 1 { msg = fmt.Sprintf("%s. Please specify one command of: %s or %s", msg, strings.Join(cmdnames[:len(cmdnames)-1], ", "), cmdnames[len(cmdnames)-1]) } } else { errtype = ErrCommandRequired if len(cmdnames) == 1 { msg = fmt.Sprintf("Please specify the %s command", cmdnames[0]) } else if len(cmdnames) > 1 { msg = fmt.Sprintf("Please specify one command of: %s or %s", strings.Join(cmdnames[:len(cmdnames)-1], ", "), cmdnames[len(cmdnames)-1]) } } return newError(errtype, msg) } func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) { if !option.canArgument() { if argument != nil { return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option) } err = option.set(nil) } else if argument != nil || (canarg && !s.eof()) { var arg string if argument != nil { arg = *argument } else { arg = s.pop() if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') { return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg) } else if p.Options&PassDoubleDash != 0 && arg == "--" { return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option) } } if option.tag.Get("unquote") != "false" { arg, err = unquoteIfPossible(arg) } if err == nil { err = option.set(&arg) } } else if option.OptionalArgument { option.empty() for _, v := range option.OptionalValue { err = option.set(&v) if err != nil { break } } } else { err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option) } if err != nil { if _, ok := err.(*Error); !ok { err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s", option, option.value.Type(), err.Error()) } } return err } func (p *Parser) parseLong(s *parseState, name string, argument *string) error { if option := s.lookup.longNames[name]; option != nil { // Only long options that are required can consume an argument // from the argument list canarg := !option.OptionalArgument return p.parseOption(s, name, option, canarg, argument) } return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name) } func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) { c, n := utf8.DecodeRuneInString(optname) if n == len(optname) { return optname, nil } first := string(c) if option := s.lookup.shortNames[first]; option != nil && option.canArgument() { arg := optname[n:] return first, &arg } return optname, nil } func (p *Parser) parseShort(s *parseState, optname string, argument *string) error { if argument == nil { optname, argument = p.splitShortConcatArg(s, optname) } for i, c := range optname { shortname := string(c) if option := s.lookup.shortNames[shortname]; option != nil { // Only the last short argument can consume an argument from // the arguments list, and only if it's non optional canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument if err := p.parseOption(s, shortname, option, canarg, argument); err != nil { return err } } else { return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname) } // Only the first option can have a concatted argument, so just // clear argument here argument = nil } return nil } func (p *parseState) addArgs(args ...string) error { for len(p.positional) > 0 && len(args) > 0 { arg := p.positional[0] if err := convert(args[0], arg.value, arg.tag); err != nil { p.err = err return err } if !arg.isRemaining() { p.positional = p.positional[1:] } args = args[1:] } p.retargs = append(p.retargs, args...) return nil } func (p *Parser) parseNonOption(s *parseState) error { if len(s.positional) > 0 { return s.addArgs(s.arg) } if len(s.command.commands) > 0 && len(s.retargs) == 0 { if cmd := s.lookup.commands[s.arg]; cmd != nil { s.command.Active = cmd cmd.fillParseState(s) return nil } else if !s.command.SubcommandsOptional { s.addArgs(s.arg) return newErrorf(ErrUnknownCommand, "Unknown command `%s'", s.arg) } } if (p.Options & PassAfterNonOption) != None { // If PassAfterNonOption is set then all remaining arguments // are considered positional if err := s.addArgs(s.arg); err != nil { return err } if err := s.addArgs(s.args...); err != nil { return err } s.args = []string{} } else { return s.addArgs(s.arg) } return nil } func (p *Parser) showBuiltinHelp() error { var b bytes.Buffer p.WriteHelp(&b) return newError(ErrHelp, b.String()) } func (p *Parser) printError(err error) error { if err != nil && (p.Options&PrintErrors) != None { flagsErr, ok := err.(*Error) if ok && flagsErr.Type == ErrHelp { fmt.Fprintln(os.Stdout, err) } else { fmt.Fprintln(os.Stderr, err) } } return err } func (p *Parser) clearIsSet() { p.eachCommand(func(c *Command) { c.eachGroup(func(g *Group) { for _, option := range g.options { option.isSet = false } }) }, true) }