1// 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}
715