1 package arg
2 3 import (
4 "encoding"
5 "encoding/csv"
6 "errors"
7 "fmt"
8 "io"
9 "os"
10 "path/filepath"
11 "reflect"
12 "strings"
13 14 scalar "github.com/alexflint/go-scalar"
15 )
16 17 // path represents a sequence of steps to find the output location for an
18 // argument or subcommand in the final destination struct
19 type path struct {
20 root int // index of the destination struct
21 fields []reflect.StructField // sequence of struct fields to traverse
22 }
23 24 // String gets a string representation of the given path
25 func (p path) String() string {
26 s := "args"
27 for _, f := range p.fields {
28 s += "." + f.Name
29 }
30 return s
31 }
32 33 // Child gets a new path representing a child of this path.
34 func (p path) Child(f reflect.StructField) path {
35 // copy the entire slice of fields to avoid possible slice overwrite
36 subfields := make([]reflect.StructField, len(p.fields)+1)
37 copy(subfields, p.fields)
38 subfields[len(subfields)-1] = f
39 return path{
40 root: p.root,
41 fields: subfields,
42 }
43 }
44 45 // spec represents a command line option
46 type spec struct {
47 dest path
48 field reflect.StructField // the struct field from which this option was created
49 long string // the --long form for this option, or empty if none
50 short string // the -s short form for this option, or empty if none
51 cardinality cardinality // determines how many tokens will be present (possible values: zero, one, multiple)
52 required bool // if true, this option must be present on the command line
53 positional bool // if true, this option will be looked for in the positional flags
54 separate bool // if true, each slice and map entry will have its own --flag
55 help string // the help text for this option
56 hidden bool // if true, this option will be hidden from help text
57 env string // the name of the environment variable for this option, or empty for none
58 defaultValue reflect.Value // default value for this option
59 defaultString string // default value for this option, in string form to be displayed in help text
60 placeholder string // placeholder string in help
61 }
62 63 // command represents a named subcommand, or the top-level command
64 type command struct {
65 name string
66 aliases []string
67 help string
68 dest path
69 specs []*spec
70 subcommands []*command
71 parent *command
72 hidden bool
73 }
74 75 // ErrHelp indicates that the builtin -h or --help were provided
76 var ErrHelp = errors.New("help requested by user")
77 78 // ErrVersion indicates that the builtin --version was provided
79 var ErrVersion = errors.New("version requested by user")
80 81 // for monkey patching in example and test code
82 var mustParseExit = os.Exit
83 var mustParseOut io.Writer = os.Stdout
84 85 // MustParse processes command line arguments and exits upon failure
86 func MustParse(dest ...interface{}) *Parser {
87 return mustParse(Config{Exit: mustParseExit, Out: mustParseOut}, dest...)
88 }
89 90 // mustParse is a helper that facilitates testing
91 func mustParse(config Config, dest ...interface{}) *Parser {
92 dests := append(registrations, dest...)
93 p, err := NewParser(config, dests...)
94 if err != nil {
95 fmt.Fprintln(config.Out, err)
96 config.Exit(2)
97 return nil
98 }
99 100 p.MustParse(flags())
101 return p
102 }
103 104 // Parse processes command line arguments and stores them in dest
105 func Parse(dest ...interface{}) error {
106 dests := append(registrations, dest...)
107 p, err := NewParser(Config{}, dests...)
108 if err != nil {
109 return err
110 }
111 return p.Parse(flags())
112 }
113 114 // flags gets all command line arguments other than the first (program name)
115 func flags() []string {
116 if len(os.Args) == 0 { // os.Args could be empty
117 return nil
118 }
119 return os.Args[1:]
120 }
121 122 // Config represents configuration options for an argument parser
123 type Config struct {
124 // Program is the name of the program used in the help text
125 Program string
126 127 // IgnoreEnv instructs the library not to read environment variables
128 IgnoreEnv bool
129 130 // IgnoreDefault instructs the library not to reset the variables to the
131 // default values, including pointers to sub commands
132 IgnoreDefault bool
133 134 // StrictSubcommands intructs the library not to allow global commands after
135 // subcommand
136 StrictSubcommands bool
137 138 // EnvPrefix instructs the library to use a name prefix when reading environment variables.
139 EnvPrefix string
140 141 // Exit is called to terminate the process with an error code (defaults to os.Exit)
142 Exit func(int)
143 144 // Out is where help text, usage text, and failure messages are printed (defaults to os.Stdout)
145 Out io.Writer
146 }
147 148 // Parser represents a set of command line options with destination values
149 type Parser struct {
150 cmd *command
151 roots []reflect.Value
152 config Config
153 version string
154 description string
155 epilogue string
156 157 // the following field changes during processing of command line arguments
158 subcommand []string
159 }
160 161 // Versioned is the interface that the destination struct should implement to
162 // make a version string appear at the top of the help message.
163 type Versioned interface {
164 // Version returns the version string that will be printed on a line by itself
165 // at the top of the help message.
166 Version() string
167 }
168 169 // Described is the interface that the destination struct should implement to
170 // make a description string appear at the top of the help message.
171 type Described interface {
172 // Description returns the string that will be printed on a line by itself
173 // at the top of the help message.
174 Description() string
175 }
176 177 // Epilogued is the interface that the destination struct should implement to
178 // add an epilogue string at the bottom of the help message.
179 type Epilogued interface {
180 // Epilogue returns the string that will be printed on a line by itself
181 // at the end of the help message.
182 Epilogue() string
183 }
184 185 // walkFields calls a function for each field of a struct, recursively expanding struct fields.
186 func walkFields(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool) {
187 walkFieldsImpl(t, visit, nil)
188 }
189 190 func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool, path []int) {
191 for i := 0; i < t.NumField(); i++ {
192 field := t.Field(i)
193 field.Index = make([]int, len(path)+1)
194 copy(field.Index, append(path, i))
195 expand := visit(field, t)
196 if expand && field.Type.Kind() == reflect.Struct {
197 var subpath []int
198 if field.Anonymous {
199 subpath = append(path, i)
200 }
201 walkFieldsImpl(field.Type, visit, subpath)
202 }
203 }
204 }
205 206 // NewParser constructs a parser from a list of destination structs
207 func NewParser(config Config, dests ...interface{}) (*Parser, error) {
208 // fill in defaults
209 if config.Exit == nil {
210 config.Exit = os.Exit
211 }
212 if config.Out == nil {
213 config.Out = os.Stdout
214 }
215 216 // first pick a name for the command for use in the usage text
217 var name string
218 switch {
219 case config.Program != "":
220 name = config.Program
221 case len(os.Args) > 0:
222 name = filepath.Base(os.Args[0])
223 default:
224 name = "program"
225 }
226 227 // construct a parser
228 p := Parser{
229 cmd: &command{name: name},
230 config: config,
231 }
232 233 // make a list of roots
234 for _, dest := range dests {
235 p.roots = append(p.roots, reflect.ValueOf(dest))
236 }
237 238 // process each of the destination values
239 for i, dest := range dests {
240 t := reflect.TypeOf(dest)
241 if t.Kind() != reflect.Ptr {
242 panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t))
243 }
244 245 cmd, err := cmdFromStruct(name, path{root: i}, t, config.EnvPrefix)
246 if err != nil {
247 return nil, err
248 }
249 250 // for backwards compatibility, add nonzero field values as defaults
251 // this applies only to the top-level command, not to subcommands (this inconsistency
252 // is the reason that this method for setting default values was deprecated)
253 for _, spec := range cmd.specs {
254 // get the value
255 v := p.val(spec.dest)
256 257 // if the value is the "zero value" (e.g. nil pointer, empty struct) then ignore
258 if isZero(v) {
259 continue
260 }
261 262 // store as a default
263 spec.defaultValue = v
264 265 // we need a string to display in help text
266 // if MarshalText is implemented then use that
267 if m, ok := v.Interface().(encoding.TextMarshaler); ok {
268 s, err := m.MarshalText()
269 if err != nil {
270 return nil, fmt.Errorf("%v: error marshaling default value to string: %v", spec.dest, err)
271 }
272 spec.defaultString = string(s)
273 } else {
274 spec.defaultString = fmt.Sprintf("%v", v)
275 }
276 }
277 278 p.cmd.specs = append(p.cmd.specs, cmd.specs...)
279 p.cmd.subcommands = append(p.cmd.subcommands, cmd.subcommands...)
280 281 if dest, ok := dest.(Versioned); ok {
282 p.version = dest.Version()
283 }
284 if dest, ok := dest.(Described); ok {
285 p.description = dest.Description()
286 }
287 if dest, ok := dest.(Epilogued); ok {
288 p.epilogue = dest.Epilogue()
289 }
290 }
291 292 // Set the parent of the subcommands to be the top-level command
293 // to make sure that global options work when there is more than one
294 // dest supplied.
295 for _, subcommand := range p.cmd.subcommands {
296 subcommand.parent = p.cmd
297 }
298 299 return &p, nil
300 }
301 302 func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*command, error) {
303 // commands can only be created from pointers to structs
304 if t.Kind() != reflect.Ptr {
305 return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a %s",
306 dest, t.Kind())
307 }
308 309 t = t.Elem()
310 if t.Kind() != reflect.Struct {
311 return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a pointer to %s",
312 dest, t.Kind())
313 }
314 315 cmd := command{
316 name: name,
317 dest: dest,
318 }
319 320 var errs []string
321 var multiPositional string
322 walkFields(t, func(field reflect.StructField, t reflect.Type) bool {
323 // check for the ignore switch in the tag
324 tag := field.Tag.Get("arg")
325 if tag == "-" {
326 return false
327 }
328 329 // if this is an embedded struct then recurse into its fields, even if
330 // it is unexported, because exported fields on unexported embedded
331 // structs are still writable
332 if field.Anonymous && field.Type.Kind() == reflect.Struct {
333 return true
334 }
335 336 // ignore any other unexported field
337 if !isExported(field.Name) {
338 return false
339 }
340 341 // duplicate the entire path to avoid slice overwrites
342 subdest := dest.Child(field)
343 spec := spec{
344 dest: subdest,
345 field: field,
346 long: strings.ToLower(field.Name),
347 }
348 349 help, exists := field.Tag.Lookup("help")
350 if exists {
351 spec.help = help
352 }
353 354 // process each comma-separated part of the tag
355 var isSubcommand bool
356 for _, key := range strings.Split(tag, ",") {
357 if key == "" {
358 continue
359 }
360 key = strings.TrimLeft(key, " ")
361 var value string
362 if pos := strings.Index(key, ":"); pos != -1 {
363 value = key[pos+1:]
364 key = key[:pos]
365 }
366 367 switch {
368 case strings.HasPrefix(key, "---"):
369 errs = append(errs, fmt.Sprintf("%s.%s: too many hyphens", t.Name(), field.Name))
370 case strings.HasPrefix(key, "--"):
371 spec.long = key[2:]
372 case strings.HasPrefix(key, "-"):
373 if len(key) > 2 {
374 errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only",
375 t.Name(), field.Name))
376 return false
377 }
378 spec.short = key[1:]
379 case key == "required":
380 spec.required = true
381 case key == "positional":
382 spec.positional = true
383 if multiPositional != "" {
384 errs = append(errs, fmt.Sprintf("%s.%s: cannot have more positionals after %s, which is a positional slice or map",
385 t.Name(), field.Name, multiPositional))
386 }
387 case key == "separate":
388 spec.separate = true
389 case key == "help": // deprecated
390 spec.help = value
391 case key == "hidden":
392 spec.hidden = true
393 case key == "env":
394 // Use override name if provided
395 if value != "" {
396 spec.env = envPrefix + value
397 } else {
398 spec.env = envPrefix + strings.ToUpper(field.Name)
399 }
400 case key == "subcommand":
401 // decide on a name for the subcommand
402 var cmdnames []string
403 if value == "" {
404 cmdnames = []string{strings.ToLower(field.Name)}
405 } else {
406 cmdnames = strings.Split(value, "|")
407 }
408 for i := range cmdnames {
409 cmdnames[i] = strings.TrimSpace(cmdnames[i])
410 }
411 412 // parse the subcommand recursively
413 subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type, envPrefix)
414 if err != nil {
415 errs = append(errs, err.Error())
416 return false
417 }
418 419 subcmd.aliases = cmdnames[1:]
420 subcmd.parent = &cmd
421 subcmd.help = field.Tag.Get("help")
422 423 cmd.subcommands = append(cmd.subcommands, subcmd)
424 isSubcommand = true
425 default:
426 errs = append(errs, fmt.Sprintf("unrecognized tag '%s' on field %s", key, tag))
427 return false
428 }
429 }
430 431 // placeholder is the string used in the help text like this: "--somearg PLACEHOLDER"
432 placeholder, hasPlaceholder := field.Tag.Lookup("placeholder")
433 if hasPlaceholder {
434 spec.placeholder = placeholder
435 } else if spec.long != "" {
436 spec.placeholder = strings.ToUpper(spec.long)
437 } else {
438 spec.placeholder = strings.ToUpper(spec.field.Name)
439 }
440 441 // if this is a subcommand then we've done everything we need to do
442 if isSubcommand {
443 if spec.hidden {
444 cmd.subcommands[len(cmd.subcommands)-1].hidden = true
445 }
446 return false
447 }
448 449 // check whether this field is supported. It's good to do this here rather than
450 // wait until ParseValue because it means that a program with invalid argument
451 // fields will always fail regardless of whether the arguments it received
452 // exercised those fields.
453 var err error
454 spec.cardinality, err = cardinalityOf(field.Type)
455 if err != nil {
456 errs = append(errs, fmt.Sprintf("%s.%s: %s fields are not supported",
457 t.Name(), field.Name, field.Type.String()))
458 return false
459 }
460 461 // record the existence of a slice or map that will consume all remaining
462 // positional arguments so that we can throw an error if further positionals
463 // are found later
464 if spec.cardinality == multiple && spec.positional {
465 multiPositional = t.Name() + "." + field.Name
466 }
467 468 defaultString, hasDefault := field.Tag.Lookup("default")
469 if hasDefault {
470 // we do not support default values for maps and slices
471 if spec.cardinality == multiple {
472 errs = append(errs, fmt.Sprintf("%s.%s: default values are not supported for slice or map fields",
473 t.Name(), field.Name))
474 return false
475 }
476 477 // a required field cannot also have a default value
478 if spec.required {
479 errs = append(errs, fmt.Sprintf("%s.%s: 'required' cannot be used when a default value is specified",
480 t.Name(), field.Name))
481 return false
482 }
483 484 // parse the default value
485 spec.defaultString = defaultString
486 if field.Type.Kind() == reflect.Ptr {
487 // here we have a field of type *T and we create a new T, no need to dereference
488 // in order for the value to be settable
489 spec.defaultValue = reflect.New(field.Type.Elem())
490 } else {
491 // here we have a field of type T and we create a new T and then dereference it
492 // so that the resulting value is settable
493 spec.defaultValue = reflect.New(field.Type).Elem()
494 }
495 err := scalar.ParseValue(spec.defaultValue, defaultString)
496 if err != nil {
497 errs = append(errs, fmt.Sprintf("%s.%s: error processing default value: %v", t.Name(), field.Name, err))
498 return false
499 }
500 }
501 502 // add the spec to the list of specs
503 cmd.specs = append(cmd.specs, &spec)
504 505 // if this was an embedded field then we already returned true up above
506 return false
507 })
508 509 if len(errs) > 0 {
510 return nil, errors.New(strings.Join(errs, "\n"))
511 }
512 513 // check that we don't have both positionals and subcommands
514 var hasPositional bool
515 for _, spec := range cmd.specs {
516 if spec.positional {
517 hasPositional = true
518 }
519 }
520 if hasPositional && len(cmd.subcommands) > 0 {
521 return nil, fmt.Errorf("%s cannot have both subcommands and positional arguments", dest)
522 }
523 524 return &cmd, nil
525 }
526 527 // Parse processes the given command line option, storing the results in the fields
528 // of the structs from which NewParser was constructed.
529 //
530 // It returns ErrHelp if "--help" is one of the command line args and ErrVersion if
531 // "--version" is one of the command line args (the latter only applies if the
532 // destination struct passed to NewParser implements Versioned.)
533 //
534 // To respond to --help and --version in the way that MustParse does, see examples
535 // in the README under "Custom handling of --help and --version".
536 func (p *Parser) Parse(args []string) error {
537 err := p.process(args)
538 if err != nil {
539 // If -h or --help were specified then make sure help text supercedes other errors
540 for _, arg := range args {
541 if arg == "-h" || arg == "--help" {
542 return ErrHelp
543 }
544 if arg == "--" {
545 break
546 }
547 }
548 }
549 return err
550 }
551 552 func (p *Parser) MustParse(args []string) {
553 err := p.Parse(args)
554 switch {
555 case err == ErrHelp:
556 p.WriteHelpForSubcommand(p.config.Out, p.subcommand...)
557 p.config.Exit(0)
558 case err == ErrVersion:
559 fmt.Fprintln(p.config.Out, p.version)
560 p.config.Exit(0)
561 case err != nil:
562 p.FailSubcommand(err.Error(), p.subcommand...)
563 }
564 }
565 566 // process environment vars for the given arguments
567 func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error {
568 for _, spec := range specs {
569 if spec.env == "" {
570 continue
571 }
572 573 value, found := os.LookupEnv(spec.env)
574 if !found {
575 continue
576 }
577 578 if spec.cardinality == multiple {
579 // expect a CSV string in an environment
580 // variable in the case of multiple values
581 var values []string
582 var err error
583 if len(strings.TrimSpace(value)) > 0 {
584 values, err = csv.NewReader(strings.NewReader(value)).Read()
585 if err != nil {
586 return fmt.Errorf(
587 "error reading a CSV string from environment variable %s with multiple values: %v",
588 spec.env,
589 err,
590 )
591 }
592 }
593 if err = setSliceOrMap(p.val(spec.dest), values, !spec.separate); err != nil {
594 return fmt.Errorf(
595 "error processing environment variable %s with multiple values: %v",
596 spec.env,
597 err,
598 )
599 }
600 } else {
601 if err := scalar.ParseValue(p.val(spec.dest), value); err != nil {
602 return fmt.Errorf("error processing environment variable %s: %v", spec.env, err)
603 }
604 }
605 wasPresent[spec] = true
606 }
607 608 return nil
609 }
610 611 // process goes through arguments one-by-one, parses them, and assigns the result to
612 // the underlying struct field
613 func (p *Parser) process(args []string) error {
614 // track the options we have seen
615 wasPresent := make(map[*spec]bool)
616 617 // union of specs for the chain of subcommands encountered so far
618 curCmd := p.cmd
619 p.subcommand = nil
620 621 // make a copy of the specs because we will add to this list each time we expand a subcommand
622 specs := make([]*spec, len(curCmd.specs))
623 copy(specs, curCmd.specs)
624 625 // deal with environment vars
626 if !p.config.IgnoreEnv {
627 err := p.captureEnvVars(specs, wasPresent)
628 if err != nil {
629 return err
630 }
631 }
632 633 // determine if the current command has a version option spec
634 var hasVersionOption bool
635 for _, spec := range curCmd.specs {
636 if spec.long == "version" {
637 hasVersionOption = true
638 break
639 }
640 }
641 642 // process each string from the command line
643 var allpositional bool
644 var positionals []string
645 646 // must use explicit for loop, not range, because we manipulate i inside the loop
647 for i := 0; i < len(args); i++ {
648 arg := args[i]
649 if arg == "--" && !allpositional {
650 allpositional = true
651 continue
652 }
653 654 if !isFlag(arg) || allpositional {
655 // each subcommand can have either subcommands or positionals, but not both
656 if len(curCmd.subcommands) == 0 {
657 positionals = append(positionals, arg)
658 continue
659 }
660 661 // if we have a subcommand then make sure it is valid for the current context
662 subcmd := findSubcommand(curCmd.subcommands, arg)
663 if subcmd == nil {
664 return fmt.Errorf("invalid subcommand: %s", arg)
665 }
666 667 // instantiate the field to point to a new struct
668 v := p.val(subcmd.dest)
669 if v.IsNil() {
670 v.Set(reflect.New(v.Type().Elem())) // we already checked that all subcommands are struct pointers
671 }
672 673 // add the new options to the set of allowed options
674 if p.config.StrictSubcommands {
675 specs = make([]*spec, len(subcmd.specs))
676 copy(specs, subcmd.specs)
677 } else {
678 specs = append(specs, subcmd.specs...)
679 }
680 681 // capture environment vars for these new options
682 if !p.config.IgnoreEnv {
683 err := p.captureEnvVars(subcmd.specs, wasPresent)
684 if err != nil {
685 return err
686 }
687 }
688 689 curCmd = subcmd
690 p.subcommand = append(p.subcommand, arg)
691 continue
692 }
693 694 // check for special --help and --version flags
695 switch arg {
696 case "-h", "--help":
697 return ErrHelp
698 case "--version":
699 if !hasVersionOption && p.version != "" {
700 return ErrVersion
701 }
702 }
703 704 // check for an equals sign, as in "--foo=bar"
705 var value string
706 opt := strings.TrimLeft(arg, "-")
707 if pos := strings.Index(opt, "="); pos != -1 {
708 value = opt[pos+1:]
709 opt = opt[:pos]
710 }
711 712 // lookup the spec for this option (note that the "specs" slice changes as
713 // we expand subcommands so it is better not to use a map)
714 spec := findOption(specs, opt)
715 if spec == nil || opt == "" {
716 return fmt.Errorf("unknown argument %s", arg)
717 }
718 wasPresent[spec] = true
719 720 // deal with the case of multiple values
721 if spec.cardinality == multiple {
722 var values []string
723 if value == "" {
724 for i+1 < len(args) && isValue(args[i+1], spec.field.Type, specs) && args[i+1] != "--" {
725 values = append(values, args[i+1])
726 i++
727 if spec.separate {
728 break
729 }
730 }
731 } else {
732 values = append(values, value)
733 }
734 err := setSliceOrMap(p.val(spec.dest), values, !spec.separate)
735 if err != nil {
736 return fmt.Errorf("error processing %s: %v", arg, err)
737 }
738 continue
739 }
740 741 // if it's a flag and it has no value then set the value to true
742 // use boolean because this takes account of TextUnmarshaler
743 if spec.cardinality == zero && value == "" {
744 value = "true"
745 }
746 747 // if we have something like "--foo" then the value is the next argument
748 if value == "" {
749 if i+1 == len(args) {
750 return fmt.Errorf("missing value for %s", arg)
751 }
752 if !isValue(args[i+1], spec.field.Type, specs) {
753 return fmt.Errorf("missing value for %s", arg)
754 }
755 value = args[i+1]
756 i++
757 }
758 759 err := scalar.ParseValue(p.val(spec.dest), value)
760 if err != nil {
761 return fmt.Errorf("error processing %s: %v", arg, err)
762 }
763 }
764 765 // process positionals
766 for _, spec := range specs {
767 if !spec.positional {
768 continue
769 }
770 if len(positionals) == 0 {
771 break
772 }
773 wasPresent[spec] = true
774 if spec.cardinality == multiple {
775 err := setSliceOrMap(p.val(spec.dest), positionals, true)
776 if err != nil {
777 return fmt.Errorf("error processing %s: %v", spec.placeholder, err)
778 }
779 positionals = nil
780 } else {
781 err := scalar.ParseValue(p.val(spec.dest), positionals[0])
782 if err != nil {
783 return fmt.Errorf("error processing %s: %v", spec.placeholder, err)
784 }
785 positionals = positionals[1:]
786 }
787 }
788 if len(positionals) > 0 {
789 return fmt.Errorf("too many positional arguments at '%s'", positionals[0])
790 }
791 792 // fill in defaults and check that all the required args were provided
793 for _, spec := range specs {
794 if wasPresent[spec] {
795 continue
796 }
797 798 if spec.required {
799 if spec.short == "" && spec.long == "" {
800 msg := fmt.Sprintf("environment variable %s is required", spec.env)
801 return errors.New(msg)
802 }
803 804 msg := fmt.Sprintf("%s is required", spec.placeholder)
805 if spec.env != "" {
806 msg += " (or environment variable " + spec.env + ")"
807 }
808 809 return errors.New(msg)
810 }
811 812 if spec.defaultValue.IsValid() && !p.config.IgnoreDefault {
813 // One issue here is that if the user now modifies the value then
814 // the default value stored in the spec will be corrupted. There
815 // is no general way to "deep-copy" values in Go, and we still
816 // support the old-style method for specifying defaults as
817 // Go values assigned directly to the struct field, so we are stuck.
818 p.val(spec.dest).Set(spec.defaultValue)
819 }
820 }
821 822 return nil
823 }
824 825 // isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--"
826 func isFlag(s string) bool {
827 return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != ""
828 }
829 830 // isValue returns true if a token should be consumed as a value for a flag of type t. This
831 // is almost always the inverse of isFlag. The one exception is for negative numbers, in which
832 // case we check the list of active options and return true if its not present there.
833 func isValue(s string, t reflect.Type, specs []*spec) bool {
834 switch t.Kind() {
835 case reflect.Ptr, reflect.Slice:
836 return isValue(s, t.Elem(), specs)
837 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
838 v := reflect.New(t)
839 err := scalar.ParseValue(v, s)
840 // if value can be parsed and is not an explicit option declared elsewhere, then use it as a value
841 if err == nil && (!strings.HasPrefix(s, "-") || findOption(specs, strings.TrimPrefix(s, "-")) == nil) {
842 return true
843 }
844 }
845 846 // default case that is used in all cases other than negative numbers: inverse of isFlag
847 return !isFlag(s)
848 }
849 850 // val returns a reflect.Value corresponding to the current value for the
851 // given path
852 func (p *Parser) val(dest path) reflect.Value {
853 v := p.roots[dest.root]
854 for _, field := range dest.fields {
855 if v.Kind() == reflect.Ptr {
856 if v.IsNil() {
857 return reflect.Value{}
858 }
859 v = v.Elem()
860 }
861 862 v = v.FieldByIndex(field.Index)
863 }
864 return v
865 }
866 867 // findOption finds an option from its name, or returns null if no spec is found
868 func findOption(specs []*spec, name string) *spec {
869 for _, spec := range specs {
870 if spec.positional {
871 continue
872 }
873 if spec.long == name || spec.short == name {
874 return spec
875 }
876 }
877 return nil
878 }
879 880 // findSubcommand finds a subcommand using its name, or returns null if no subcommand is found
881 func findSubcommand(cmds []*command, name string) *command {
882 for _, cmd := range cmds {
883 if cmd.name == name {
884 return cmd
885 }
886 for _, alias := range cmd.aliases {
887 if alias == name {
888 return cmd
889 }
890 }
891 }
892 return nil
893 }
894