blob: 3fc3f7ba1c2e2c8b3ab295564755e7d87483e628 [file] [log] [blame]
Naveen Sampath04696f72022-06-13 15:19:14 +05301// 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 "reflect"
13 "sort"
14 "strings"
15 "unicode/utf8"
16)
17
18// A Parser provides command line option parsing. It can contain several
19// option groups each with their own set of options.
20type Parser struct {
21 // Embedded, see Command for more information
22 *Command
23
24 // A usage string to be displayed in the help message.
25 Usage string
26
27 // Option flags changing the behavior of the parser.
28 Options Options
29
30 // NamespaceDelimiter separates group namespaces and option long names
31 NamespaceDelimiter string
32
33 // EnvNamespaceDelimiter separates group env namespaces and env keys
34 EnvNamespaceDelimiter string
35
36 // UnknownOptionsHandler is a function which gets called when the parser
37 // encounters an unknown option. The function receives the unknown option
38 // name, a SplitArgument which specifies its value if set with an argument
39 // separator, and the remaining command line arguments.
40 // It should return a new list of remaining arguments to continue parsing,
41 // or an error to indicate a parse failure.
42 UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error)
43
44 // CompletionHandler is a function gets called to handle the completion of
45 // items. By default, the items are printed and the application is exited.
46 // You can override this default behavior by specifying a custom CompletionHandler.
47 CompletionHandler func(items []Completion)
48
49 // CommandHandler is a function that gets called to handle execution of a
50 // command. By default, the command will simply be executed. This can be
51 // overridden to perform certain actions (such as applying global flags)
52 // just before the command is executed. Note that if you override the
53 // handler it is your responsibility to call the command.Execute function.
54 //
55 // The command passed into CommandHandler may be nil in case there is no
56 // command to be executed when parsing has finished.
57 CommandHandler func(command Commander, args []string) error
58
59 internalError error
60}
61
62// SplitArgument represents the argument value of an option that was passed using
63// an argument separator.
64type SplitArgument interface {
65 // String returns the option's value as a string, and a boolean indicating
66 // if the option was present.
67 Value() (string, bool)
68}
69
70type strArgument struct {
71 value *string
72}
73
74func (s strArgument) Value() (string, bool) {
75 if s.value == nil {
76 return "", false
77 }
78
79 return *s.value, true
80}
81
82// Options provides parser options that change the behavior of the option
83// parser.
84type Options uint
85
86const (
87 // None indicates no options.
88 None Options = 0
89
90 // HelpFlag adds a default Help Options group to the parser containing
91 // -h and --help options. When either -h or --help is specified on the
92 // command line, the parser will return the special error of type
93 // ErrHelp. When PrintErrors is also specified, then the help message
94 // will also be automatically printed to os.Stdout.
95 HelpFlag = 1 << iota
96
97 // PassDoubleDash passes all arguments after a double dash, --, as
98 // remaining command line arguments (i.e. they will not be parsed for
99 // flags).
100 PassDoubleDash
101
102 // IgnoreUnknown ignores any unknown options and passes them as
103 // remaining command line arguments instead of generating an error.
104 IgnoreUnknown
105
106 // PrintErrors prints any errors which occurred during parsing to
107 // os.Stderr. In the special case of ErrHelp, the message will be printed
108 // to os.Stdout.
109 PrintErrors
110
111 // PassAfterNonOption passes all arguments after the first non option
112 // as remaining command line arguments. This is equivalent to strict
113 // POSIX processing.
114 PassAfterNonOption
115
116 // Default is a convenient default set of options which should cover
117 // most of the uses of the flags package.
118 Default = HelpFlag | PrintErrors | PassDoubleDash
119)
120
121type parseState struct {
122 arg string
123 args []string
124 retargs []string
125 positional []*Arg
126 err error
127
128 command *Command
129 lookup lookup
130}
131
132// Parse is a convenience function to parse command line options with default
133// settings. The provided data is a pointer to a struct representing the
134// default option group (named "Application Options"). For more control, use
135// flags.NewParser.
136func Parse(data interface{}) ([]string, error) {
137 return NewParser(data, Default).Parse()
138}
139
140// ParseArgs is a convenience function to parse command line options with default
141// settings. The provided data is a pointer to a struct representing the
142// default option group (named "Application Options"). The args argument is
143// the list of command line arguments to parse. If you just want to parse the
144// default program command line arguments (i.e. os.Args), then use flags.Parse
145// instead. For more control, use flags.NewParser.
146func ParseArgs(data interface{}, args []string) ([]string, error) {
147 return NewParser(data, Default).ParseArgs(args)
148}
149
150// NewParser creates a new parser. It uses os.Args[0] as the application
151// name and then calls Parser.NewNamedParser (see Parser.NewNamedParser for
152// more details). The provided data is a pointer to a struct representing the
153// default option group (named "Application Options"), or nil if the default
154// group should not be added. The options parameter specifies a set of options
155// for the parser.
156func NewParser(data interface{}, options Options) *Parser {
157 p := NewNamedParser(path.Base(os.Args[0]), options)
158
159 if data != nil {
160 g, err := p.AddGroup("Application Options", "", data)
161
162 if err == nil {
163 g.parent = p
164 }
165
166 p.internalError = err
167 }
168
169 return p
170}
171
172// NewNamedParser creates a new parser. The appname is used to display the
173// executable name in the built-in help message. Option groups and commands can
174// be added to this parser by using AddGroup and AddCommand.
175func NewNamedParser(appname string, options Options) *Parser {
176 p := &Parser{
177 Command: newCommand(appname, "", "", nil),
178 Options: options,
179 NamespaceDelimiter: ".",
180 EnvNamespaceDelimiter: "_",
181 }
182
183 p.Command.parent = p
184
185 return p
186}
187
188// Parse parses the command line arguments from os.Args using Parser.ParseArgs.
189// For more detailed information see ParseArgs.
190func (p *Parser) Parse() ([]string, error) {
191 return p.ParseArgs(os.Args[1:])
192}
193
194// ParseArgs parses the command line arguments according to the option groups that
195// were added to the parser. On successful parsing of the arguments, the
196// remaining, non-option, arguments (if any) are returned. The returned error
197// indicates a parsing error and can be used with PrintError to display
198// contextual information on where the error occurred exactly.
199//
200// When the common help group has been added (AddHelp) and either -h or --help
201// was specified in the command line arguments, a help message will be
202// automatically printed if the PrintErrors option is enabled.
203// Furthermore, the special error type ErrHelp is returned.
204// It is up to the caller to exit the program if so desired.
205func (p *Parser) ParseArgs(args []string) ([]string, error) {
206 if p.internalError != nil {
207 return nil, p.internalError
208 }
209
210 p.eachOption(func(c *Command, g *Group, option *Option) {
211 option.clearReferenceBeforeSet = true
212 option.updateDefaultLiteral()
213 })
214
215 // Add built-in help group to all commands if necessary
216 if (p.Options & HelpFlag) != None {
217 p.addHelpGroups(p.showBuiltinHelp)
218 }
219
220 compval := os.Getenv("GO_FLAGS_COMPLETION")
221
222 if len(compval) != 0 {
223 comp := &completion{parser: p}
224 items := comp.complete(args)
225
226 if p.CompletionHandler != nil {
227 p.CompletionHandler(items)
228 } else {
229 comp.print(items, compval == "verbose")
230 os.Exit(0)
231 }
232
233 return nil, nil
234 }
235
236 s := &parseState{
237 args: args,
238 retargs: make([]string, 0, len(args)),
239 }
240
241 p.fillParseState(s)
242
243 for !s.eof() {
244 var err error
245 arg := s.pop()
246
247 // When PassDoubleDash is set and we encounter a --, then
248 // simply append all the rest as arguments and break out
249 if (p.Options&PassDoubleDash) != None && arg == "--" {
250 s.addArgs(s.args...)
251 break
252 }
253
254 if !argumentIsOption(arg) {
255 if (p.Options&PassAfterNonOption) != None && s.lookup.commands[arg] == nil {
256 // If PassAfterNonOption is set then all remaining arguments
257 // are considered positional
258 if err = s.addArgs(s.arg); err != nil {
259 break
260 }
261
262 if err = s.addArgs(s.args...); err != nil {
263 break
264 }
265
266 break
267 }
268
269 // Note: this also sets s.err, so we can just check for
270 // nil here and use s.err later
271 if p.parseNonOption(s) != nil {
272 break
273 }
274
275 continue
276 }
277
278 prefix, optname, islong := stripOptionPrefix(arg)
279 optname, _, argument := splitOption(prefix, optname, islong)
280
281 if islong {
282 err = p.parseLong(s, optname, argument)
283 } else {
284 err = p.parseShort(s, optname, argument)
285 }
286
287 if err != nil {
288 ignoreUnknown := (p.Options & IgnoreUnknown) != None
289 parseErr := wrapError(err)
290
291 if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) {
292 s.err = parseErr
293 break
294 }
295
296 if ignoreUnknown {
297 s.addArgs(arg)
298 } else if p.UnknownOptionHandler != nil {
299 modifiedArgs, err := p.UnknownOptionHandler(optname, strArgument{argument}, s.args)
300
301 if err != nil {
302 s.err = err
303 break
304 }
305
306 s.args = modifiedArgs
307 }
308 }
309 }
310
311 if s.err == nil {
312 p.eachOption(func(c *Command, g *Group, option *Option) {
313 err := option.clearDefault()
314 if err != nil {
315 if _, ok := err.(*Error); !ok {
316 err = p.marshalError(option, err)
317 }
318 s.err = err
319 }
320 })
321
322 s.checkRequired(p)
323 }
324
325 var reterr error
326
327 if s.err != nil {
328 reterr = s.err
329 } else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional {
330 reterr = s.estimateCommand()
331 } else if cmd, ok := s.command.data.(Commander); ok {
332 if p.CommandHandler != nil {
333 reterr = p.CommandHandler(cmd, s.retargs)
334 } else {
335 reterr = cmd.Execute(s.retargs)
336 }
337 } else if p.CommandHandler != nil {
338 reterr = p.CommandHandler(nil, s.retargs)
339 }
340
341 if reterr != nil {
342 var retargs []string
343
344 if ourErr, ok := reterr.(*Error); !ok || ourErr.Type != ErrHelp {
345 retargs = append([]string{s.arg}, s.args...)
346 } else {
347 retargs = s.args
348 }
349
350 return retargs, p.printError(reterr)
351 }
352
353 return s.retargs, nil
354}
355
356func (p *parseState) eof() bool {
357 return len(p.args) == 0
358}
359
360func (p *parseState) pop() string {
361 if p.eof() {
362 return ""
363 }
364
365 p.arg = p.args[0]
366 p.args = p.args[1:]
367
368 return p.arg
369}
370
371func (p *parseState) peek() string {
372 if p.eof() {
373 return ""
374 }
375
376 return p.args[0]
377}
378
379func (p *parseState) checkRequired(parser *Parser) error {
380 c := parser.Command
381
382 var required []*Option
383
384 for c != nil {
385 c.eachGroup(func(g *Group) {
386 for _, option := range g.options {
387 if !option.isSet && option.Required {
388 required = append(required, option)
389 }
390 }
391 })
392
393 c = c.Active
394 }
395
396 if len(required) == 0 {
397 if len(p.positional) > 0 {
398 var reqnames []string
399
400 for _, arg := range p.positional {
401 argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1
402
403 if !argRequired {
404 continue
405 }
406
407 if arg.isRemaining() {
408 if arg.value.Len() < arg.Required {
409 var arguments string
410
411 if arg.Required > 1 {
412 arguments = "arguments, but got only " + fmt.Sprintf("%d", arg.value.Len())
413 } else {
414 arguments = "argument"
415 }
416
417 reqnames = append(reqnames, "`"+arg.Name+" (at least "+fmt.Sprintf("%d", arg.Required)+" "+arguments+")`")
418 } else if arg.RequiredMaximum != -1 && arg.value.Len() > arg.RequiredMaximum {
419 if arg.RequiredMaximum == 0 {
420 reqnames = append(reqnames, "`"+arg.Name+" (zero arguments)`")
421 } else {
422 var arguments string
423
424 if arg.RequiredMaximum > 1 {
425 arguments = "arguments, but got " + fmt.Sprintf("%d", arg.value.Len())
426 } else {
427 arguments = "argument"
428 }
429
430 reqnames = append(reqnames, "`"+arg.Name+" (at most "+fmt.Sprintf("%d", arg.RequiredMaximum)+" "+arguments+")`")
431 }
432 }
433 } else {
434 reqnames = append(reqnames, "`"+arg.Name+"`")
435 }
436 }
437
438 if len(reqnames) == 0 {
439 return nil
440 }
441
442 var msg string
443
444 if len(reqnames) == 1 {
445 msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0])
446 } else {
447 msg = fmt.Sprintf("the required arguments %s and %s were not provided",
448 strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1])
449 }
450
451 p.err = newError(ErrRequired, msg)
452 return p.err
453 }
454
455 return nil
456 }
457
458 names := make([]string, 0, len(required))
459
460 for _, k := range required {
461 names = append(names, "`"+k.String()+"'")
462 }
463
464 sort.Strings(names)
465
466 var msg string
467
468 if len(names) == 1 {
469 msg = fmt.Sprintf("the required flag %s was not specified", names[0])
470 } else {
471 msg = fmt.Sprintf("the required flags %s and %s were not specified",
472 strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
473 }
474
475 p.err = newError(ErrRequired, msg)
476 return p.err
477}
478
479func (p *parseState) estimateCommand() error {
480 commands := p.command.sortedVisibleCommands()
481 cmdnames := make([]string, len(commands))
482
483 for i, v := range commands {
484 cmdnames[i] = v.Name
485 }
486
487 var msg string
488 var errtype ErrorType
489
490 if len(p.retargs) != 0 {
491 c, l := closestChoice(p.retargs[0], cmdnames)
492 msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
493 errtype = ErrUnknownCommand
494
495 if float32(l)/float32(len(c)) < 0.5 {
496 msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c)
497 } else if len(cmdnames) == 1 {
498 msg = fmt.Sprintf("%s. You should use the %s command",
499 msg,
500 cmdnames[0])
501 } else if len(cmdnames) > 1 {
502 msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
503 msg,
504 strings.Join(cmdnames[:len(cmdnames)-1], ", "),
505 cmdnames[len(cmdnames)-1])
506 }
507 } else {
508 errtype = ErrCommandRequired
509
510 if len(cmdnames) == 1 {
511 msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
512 } else if len(cmdnames) > 1 {
513 msg = fmt.Sprintf("Please specify one command of: %s or %s",
514 strings.Join(cmdnames[:len(cmdnames)-1], ", "),
515 cmdnames[len(cmdnames)-1])
516 }
517 }
518
519 return newError(errtype, msg)
520}
521
522func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) {
523 if !option.canArgument() {
524 if argument != nil {
525 return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option)
526 }
527
528 err = option.set(nil)
529 } else if argument != nil || (canarg && !s.eof()) {
530 var arg string
531
532 if argument != nil {
533 arg = *argument
534 } else {
535 arg = s.pop()
536
537 if validationErr := option.isValidValue(arg); validationErr != nil {
538 return newErrorf(ErrExpectedArgument, validationErr.Error())
539 } else if p.Options&PassDoubleDash != 0 && arg == "--" {
540 return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
541 }
542 }
543
544 if option.tag.Get("unquote") != "false" {
545 arg, err = unquoteIfPossible(arg)
546 }
547
548 if err == nil {
549 err = option.set(&arg)
550 }
551 } else if option.OptionalArgument {
552 option.empty()
553
554 for _, v := range option.OptionalValue {
555 err = option.set(&v)
556
557 if err != nil {
558 break
559 }
560 }
561 } else {
562 err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option)
563 }
564
565 if err != nil {
566 if _, ok := err.(*Error); !ok {
567 err = p.marshalError(option, err)
568 }
569 }
570
571 return err
572}
573
574func (p *Parser) marshalError(option *Option, err error) *Error {
575 s := "invalid argument for flag `%s'"
576
577 expected := p.expectedType(option)
578
579 if expected != "" {
580 s = s + " (expected " + expected + ")"
581 }
582
583 return newErrorf(ErrMarshal, s+": %s",
584 option,
585 err.Error())
586}
587
588func (p *Parser) expectedType(option *Option) string {
589 valueType := option.value.Type()
590
591 if valueType.Kind() == reflect.Func {
592 return ""
593 }
594
595 return valueType.String()
596}
597
598func (p *Parser) parseLong(s *parseState, name string, argument *string) error {
599 if option := s.lookup.longNames[name]; option != nil {
600 // Only long options that are required can consume an argument
601 // from the argument list
602 canarg := !option.OptionalArgument
603
604 return p.parseOption(s, name, option, canarg, argument)
605 }
606
607 return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name)
608}
609
610func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) {
611 c, n := utf8.DecodeRuneInString(optname)
612
613 if n == len(optname) {
614 return optname, nil
615 }
616
617 first := string(c)
618
619 if option := s.lookup.shortNames[first]; option != nil && option.canArgument() {
620 arg := optname[n:]
621 return first, &arg
622 }
623
624 return optname, nil
625}
626
627func (p *Parser) parseShort(s *parseState, optname string, argument *string) error {
628 if argument == nil {
629 optname, argument = p.splitShortConcatArg(s, optname)
630 }
631
632 for i, c := range optname {
633 shortname := string(c)
634
635 if option := s.lookup.shortNames[shortname]; option != nil {
636 // Only the last short argument can consume an argument from
637 // the arguments list, and only if it's non optional
638 canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument
639
640 if err := p.parseOption(s, shortname, option, canarg, argument); err != nil {
641 return err
642 }
643 } else {
644 return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname)
645 }
646
647 // Only the first option can have a concatted argument, so just
648 // clear argument here
649 argument = nil
650 }
651
652 return nil
653}
654
655func (p *parseState) addArgs(args ...string) error {
656 for len(p.positional) > 0 && len(args) > 0 {
657 arg := p.positional[0]
658
659 if err := convert(args[0], arg.value, arg.tag); err != nil {
660 p.err = err
661 return err
662 }
663
664 if !arg.isRemaining() {
665 p.positional = p.positional[1:]
666 }
667
668 args = args[1:]
669 }
670
671 p.retargs = append(p.retargs, args...)
672 return nil
673}
674
675func (p *Parser) parseNonOption(s *parseState) error {
676 if len(s.positional) > 0 {
677 return s.addArgs(s.arg)
678 }
679
680 if len(s.command.commands) > 0 && len(s.retargs) == 0 {
681 if cmd := s.lookup.commands[s.arg]; cmd != nil {
682 s.command.Active = cmd
683 cmd.fillParseState(s)
684
685 return nil
686 } else if !s.command.SubcommandsOptional {
687 s.addArgs(s.arg)
688 return newErrorf(ErrUnknownCommand, "Unknown command `%s'", s.arg)
689 }
690 }
691
692 return s.addArgs(s.arg)
693}
694
695func (p *Parser) showBuiltinHelp() error {
696 var b bytes.Buffer
697
698 p.WriteHelp(&b)
699 return newError(ErrHelp, b.String())
700}
701
702func (p *Parser) printError(err error) error {
703 if err != nil && (p.Options&PrintErrors) != None {
704 flagsErr, ok := err.(*Error)
705
706 if ok && flagsErr.Type == ErrHelp {
707 fmt.Fprintln(os.Stdout, err)
708 } else {
709 fmt.Fprintln(os.Stderr, err)
710 }
711 }
712
713 return err
714}