blob: 0a7922a08a2e9580cdfedb0c728139e71a2f08b6 [file] [log] [blame]
Scott Bakerbdb962b2020-04-03 10:53:36 -07001// 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}