123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- // 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)
- }
|