700 Zeilen
18 KiB
Go
700 Zeilen
18 KiB
Go
// 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)
|
|
}
|