parse.go raw

   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