| package flags |
| |
| import ( |
| "bytes" |
| "fmt" |
| "os" |
| "strings" |
| "unicode/utf8" |
| ) |
| |
| type parseState struct { |
| arg string |
| args []string |
| retargs []string |
| err error |
| |
| command *Command |
| lookup lookup |
| } |
| |
| 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() error { |
| required := p.lookup.required |
| |
| if len(required) == 0 { |
| return nil |
| } |
| |
| names := make([]string, 0, len(required)) |
| |
| for k := range required { |
| names = append(names, "`"+k.String()+"'") |
| } |
| |
| 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.sortedCommands() |
| 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 { |
| 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 { |
| 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 { |
| msg := fmt.Sprintf("bool flag `%s' cannot have an argument", option) |
| return newError(ErrNoArgumentForBool, msg) |
| } |
| |
| err = option.set(nil) |
| } else if argument != nil { |
| err = option.set(argument) |
| } else if canarg && !s.eof() { |
| arg := s.pop() |
| err = option.set(&arg) |
| } else if option.OptionalArgument { |
| option.clear() |
| |
| for _, v := range option.OptionalValue { |
| err = option.set(&v) |
| |
| if err != nil { |
| break |
| } |
| } |
| } else { |
| msg := fmt.Sprintf("expected argument for flag `%s'", option) |
| err = newError(ErrExpectedArgument, msg) |
| } |
| |
| if err != nil { |
| if _, ok := err.(*Error); !ok { |
| msg := fmt.Sprintf("invalid argument for flag `%s' (expected %s): %s", |
| option, |
| option.value.Type(), |
| err.Error()) |
| |
| err = newError(ErrMarshal, msg) |
| } |
| } |
| |
| return err |
| } |
| |
| func (p *Parser) parseLong(s *parseState, name string, argument *string) (options []*Option, err error) { |
| if option := s.lookup.longNames[name]; option != nil { |
| options = append(options, option) |
| |
| // Only long options that are required can consume an argument |
| // from the argument list |
| canarg := !option.OptionalArgument |
| |
| err := p.parseOption(s, name, option, canarg, argument) |
| |
| return options, err |
| } |
| |
| return nil, newError(ErrUnknownFlag, fmt.Sprintf("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) (options []*Option, err 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 { |
| options = append(options, option) |
| |
| // 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 options, err |
| } |
| } else { |
| return nil, newError(ErrUnknownFlag, fmt.Sprintf("unknown flag `%s'", shortname)) |
| } |
| |
| // Only the first option can have a concatted argument, so just |
| // clear argument here |
| argument = nil |
| } |
| |
| return options, nil |
| } |
| |
| func (p *Parser) parseNonOption(s *parseState) error { |
| if cmd := s.lookup.commands[s.arg]; cmd != nil { |
| if err := s.checkRequired(); err != nil { |
| return err |
| } |
| |
| s.command.Active = cmd |
| |
| s.command = cmd |
| s.lookup = cmd.makeLookup() |
| } else if (p.Options & PassAfterNonOption) != None { |
| // If PassAfterNonOption is set then all remaining arguments |
| // are considered positional |
| s.retargs = append(append(s.retargs, s.arg), s.args...) |
| s.args = []string{} |
| } else { |
| s.retargs = append(s.retargs, 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 { |
| fmt.Fprintln(os.Stderr, err) |
| } |
| |
| return err |
| } |