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