github.com/alecthomas/kong@v0.9.1-0.20240410131203-2ab5733f1179/context.go (about) 1 package kong 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "reflect" 8 "sort" 9 "strconv" 10 "strings" 11 ) 12 13 // Path records the nodes and parsed values from the current command-line. 14 type Path struct { 15 Parent *Node 16 17 // One of these will be non-nil. 18 App *Application 19 Positional *Positional 20 Flag *Flag 21 Argument *Argument 22 Command *Command 23 24 // Flags added by this node. 25 Flags []*Flag 26 27 // True if this Path element was created as the result of a resolver. 28 Resolved bool 29 } 30 31 // Node returns the Node associated with this Path, or nil if Path is a non-Node. 32 func (p *Path) Node() *Node { 33 switch { 34 case p.App != nil: 35 return p.App.Node 36 37 case p.Argument != nil: 38 return p.Argument 39 40 case p.Command != nil: 41 return p.Command 42 } 43 return nil 44 } 45 46 // Visitable returns the Visitable for this path element. 47 func (p *Path) Visitable() Visitable { 48 switch { 49 case p.App != nil: 50 return p.App 51 52 case p.Argument != nil: 53 return p.Argument 54 55 case p.Command != nil: 56 return p.Command 57 58 case p.Flag != nil: 59 return p.Flag 60 61 case p.Positional != nil: 62 return p.Positional 63 } 64 return nil 65 } 66 67 // Context contains the current parse context. 68 type Context struct { 69 *Kong 70 // A trace through parsed nodes. 71 Path []*Path 72 // Original command-line arguments. 73 Args []string 74 // Error that occurred during trace, if any. 75 Error error 76 77 values map[*Value]reflect.Value // Temporary values during tracing. 78 bindings bindings 79 resolvers []Resolver // Extra context-specific resolvers. 80 scan *Scanner 81 } 82 83 // Trace path of "args" through the grammar tree. 84 // 85 // The returned Context will include a Path of all commands, arguments, positionals and flags. 86 // 87 // This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(), 88 // Validate() and Apply(). 89 func Trace(k *Kong, args []string) (*Context, error) { 90 c := &Context{ 91 Kong: k, 92 Args: args, 93 Path: []*Path{ 94 {App: k.Model, Flags: k.Model.Flags}, 95 }, 96 values: map[*Value]reflect.Value{}, 97 scan: Scan(args...), 98 bindings: bindings{}, 99 } 100 c.Error = c.trace(c.Model.Node) 101 return c, nil 102 } 103 104 // Bind adds bindings to the Context. 105 func (c *Context) Bind(args ...interface{}) { 106 c.bindings.add(args...) 107 } 108 109 // BindTo adds a binding to the Context. 110 // 111 // This will typically have to be called like so: 112 // 113 // BindTo(impl, (*MyInterface)(nil)) 114 func (c *Context) BindTo(impl, iface interface{}) { 115 c.bindings.addTo(impl, iface) 116 } 117 118 // BindToProvider allows binding of provider functions. 119 // 120 // This is useful when the Run() function of different commands require different values that may 121 // not all be initialisable from the main() function. 122 func (c *Context) BindToProvider(provider interface{}) error { 123 return c.bindings.addProvider(provider) 124 } 125 126 // Value returns the value for a particular path element. 127 func (c *Context) Value(path *Path) reflect.Value { 128 switch { 129 case path.Positional != nil: 130 return c.values[path.Positional] 131 case path.Flag != nil: 132 return c.values[path.Flag.Value] 133 case path.Argument != nil: 134 return c.values[path.Argument.Argument] 135 } 136 panic("can only retrieve value for flag, argument or positional") 137 } 138 139 // Selected command or argument. 140 func (c *Context) Selected() *Node { 141 var selected *Node 142 for _, path := range c.Path { 143 switch { 144 case path.Command != nil: 145 selected = path.Command 146 case path.Argument != nil: 147 selected = path.Argument 148 } 149 } 150 return selected 151 } 152 153 // Empty returns true if there were no arguments provided. 154 func (c *Context) Empty() bool { 155 for _, path := range c.Path { 156 if !path.Resolved && path.App == nil { 157 return false 158 } 159 } 160 return true 161 } 162 163 // Validate the current context. 164 func (c *Context) Validate() error { //nolint: gocyclo 165 err := Visit(c.Model, func(node Visitable, next Next) error { 166 switch node := node.(type) { 167 case *Value: 168 ok := atLeastOneEnvSet(node.Tag.Envs) 169 if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) { 170 if err := checkEnum(node, node.Target); err != nil { 171 return err 172 } 173 } 174 175 case *Flag: 176 ok := atLeastOneEnvSet(node.Tag.Envs) 177 if node.Enum != "" && (!node.Required || node.HasDefault || (len(node.Tag.Envs) != 0 && ok)) { 178 if err := checkEnum(node.Value, node.Target); err != nil { 179 return err 180 } 181 } 182 } 183 return next(nil) 184 }) 185 if err != nil { 186 return err 187 } 188 for _, el := range c.Path { 189 var ( 190 value reflect.Value 191 desc string 192 ) 193 switch node := el.Visitable().(type) { 194 case *Value: 195 value = node.Target 196 desc = node.ShortSummary() 197 198 case *Flag: 199 value = node.Target 200 desc = node.ShortSummary() 201 202 case *Application: 203 value = node.Target 204 desc = "" 205 206 case *Node: 207 value = node.Target 208 desc = node.Path() 209 } 210 if validate := isValidatable(value); validate != nil { 211 if err := validate.Validate(); err != nil { 212 if desc != "" { 213 return fmt.Errorf("%s: %w", desc, err) 214 } 215 return err 216 } 217 } 218 } 219 for _, resolver := range c.combineResolvers() { 220 if err := resolver.Validate(c.Model); err != nil { 221 return err 222 } 223 } 224 for _, path := range c.Path { 225 var value *Value 226 switch { 227 case path.Flag != nil: 228 value = path.Flag.Value 229 230 case path.Positional != nil: 231 value = path.Positional 232 } 233 if value != nil && value.Tag.Enum != "" { 234 if err := checkEnum(value, value.Target); err != nil { 235 return err 236 } 237 } 238 if err := checkMissingFlags(path.Flags); err != nil { 239 return err 240 } 241 } 242 // Check the terminal node. 243 node := c.Selected() 244 if node == nil { 245 node = c.Model.Node 246 } 247 248 // Find deepest positional argument so we can check if all required positionals have been provided. 249 positionals := 0 250 for _, path := range c.Path { 251 if path.Positional != nil { 252 positionals = path.Positional.Position + 1 253 } 254 } 255 256 if err := checkMissingChildren(node); err != nil { 257 return err 258 } 259 if err := checkMissingPositionals(positionals, node.Positional); err != nil { 260 return err 261 } 262 if err := checkXorDuplicates(c.Path); err != nil { 263 return err 264 } 265 266 if node.Type == ArgumentNode { 267 value := node.Argument 268 if value.Required && !value.Set { 269 return fmt.Errorf("%s is required", node.Summary()) 270 } 271 } 272 return nil 273 } 274 275 // Flags returns the accumulated available flags. 276 func (c *Context) Flags() (flags []*Flag) { 277 for _, trace := range c.Path { 278 flags = append(flags, trace.Flags...) 279 } 280 return 281 } 282 283 // Command returns the full command path. 284 func (c *Context) Command() string { 285 command := []string{} 286 for _, trace := range c.Path { 287 switch { 288 case trace.Positional != nil: 289 command = append(command, "<"+trace.Positional.Name+">") 290 291 case trace.Argument != nil: 292 command = append(command, "<"+trace.Argument.Name+">") 293 294 case trace.Command != nil: 295 command = append(command, trace.Command.Name) 296 } 297 } 298 return strings.Join(command, " ") 299 } 300 301 // AddResolver adds a context-specific resolver. 302 // 303 // This is most useful in the BeforeResolve() hook. 304 func (c *Context) AddResolver(resolver Resolver) { 305 c.resolvers = append(c.resolvers, resolver) 306 } 307 308 // FlagValue returns the set value of a flag if it was encountered and exists, or its default value. 309 func (c *Context) FlagValue(flag *Flag) interface{} { 310 for _, trace := range c.Path { 311 if trace.Flag == flag { 312 v, ok := c.values[trace.Flag.Value] 313 if !ok { 314 break 315 } 316 return v.Interface() 317 } 318 } 319 if flag.Target.IsValid() { 320 return flag.Target.Interface() 321 } 322 return flag.DefaultValue.Interface() 323 } 324 325 // Reset recursively resets values to defaults (as specified in the grammar) or the zero value. 326 func (c *Context) Reset() error { 327 return Visit(c.Model.Node, func(node Visitable, next Next) error { 328 if value, ok := node.(*Value); ok { 329 return next(value.Reset()) 330 } 331 return next(nil) 332 }) 333 } 334 335 func (c *Context) endParsing() { 336 args := []string{} 337 for { 338 token := c.scan.Pop() 339 if token.Type == EOLToken { 340 break 341 } 342 args = append(args, token.String()) 343 } 344 // Note: tokens must be pushed in reverse order. 345 for i := range args { 346 c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken) 347 } 348 } 349 350 func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo 351 positional := 0 352 node.Active = true 353 354 flags := []*Flag{} 355 flagNode := node 356 if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { 357 // Add flags of the default command if the current node has one 358 // and that default command allows args / flags without explicitly 359 // naming the command on the CLI. 360 flagNode = node.DefaultCmd 361 } 362 for _, group := range flagNode.AllFlags(false) { 363 flags = append(flags, group...) 364 } 365 366 if node.Passthrough { 367 c.endParsing() 368 } 369 370 for !c.scan.Peek().IsEOL() { 371 token := c.scan.Peek() 372 switch token.Type { 373 case UntypedToken: 374 switch v := token.Value.(type) { 375 case string: 376 377 switch { 378 case v == "-": 379 fallthrough 380 default: //nolint 381 c.scan.Pop() 382 c.scan.PushTyped(token.Value, PositionalArgumentToken) 383 384 // Indicates end of parsing. All remaining arguments are treated as positional arguments only. 385 case v == "--": 386 c.scan.Pop() 387 c.endParsing() 388 389 // Long flag. 390 case strings.HasPrefix(v, "--"): 391 c.scan.Pop() 392 // Parse it and push the tokens. 393 parts := strings.SplitN(v[2:], "=", 2) 394 if len(parts) > 1 { 395 c.scan.PushTyped(parts[1], FlagValueToken) 396 } 397 c.scan.PushTyped(parts[0], FlagToken) 398 399 // Short flag. 400 case strings.HasPrefix(v, "-"): 401 c.scan.Pop() 402 // Note: tokens must be pushed in reverse order. 403 if tail := v[2:]; tail != "" { 404 c.scan.PushTyped(tail, ShortFlagTailToken) 405 } 406 c.scan.PushTyped(v[1:2], ShortFlagToken) 407 } 408 default: 409 c.scan.Pop() 410 c.scan.PushTyped(token.Value, PositionalArgumentToken) 411 } 412 413 case ShortFlagTailToken: 414 c.scan.Pop() 415 // Note: tokens must be pushed in reverse order. 416 if tail := token.String()[1:]; tail != "" { 417 c.scan.PushTyped(tail, ShortFlagTailToken) 418 } 419 c.scan.PushTyped(token.String()[0:1], ShortFlagToken) 420 421 case FlagToken: 422 if err := c.parseFlag(flags, token.String()); err != nil { 423 return err 424 } 425 426 case ShortFlagToken: 427 if err := c.parseFlag(flags, token.String()); err != nil { 428 return err 429 } 430 431 case FlagValueToken: 432 return fmt.Errorf("unexpected flag argument %q", token.Value) 433 434 case PositionalArgumentToken: 435 candidates := []string{} 436 437 // Ensure we've consumed all positional arguments. 438 if positional < len(node.Positional) { 439 arg := node.Positional[positional] 440 441 if arg.Passthrough { 442 c.endParsing() 443 } 444 445 arg.Active = true 446 err := arg.Parse(c.scan, c.getValue(arg)) 447 if err != nil { 448 return err 449 } 450 c.Path = append(c.Path, &Path{ 451 Parent: node, 452 Positional: arg, 453 }) 454 positional++ 455 break 456 } 457 458 // Assign token value to a branch name if tagged as an alias 459 // An alias will be ignored in the case of an existing command 460 cmds := make(map[string]bool) 461 for _, branch := range node.Children { 462 if branch.Type == CommandNode { 463 cmds[branch.Name] = true 464 } 465 } 466 for _, branch := range node.Children { 467 for _, a := range branch.Aliases { 468 _, ok := cmds[a] 469 if token.Value == a && !ok { 470 token.Value = branch.Name 471 break 472 } 473 } 474 } 475 476 // After positional arguments have been consumed, check commands next... 477 for _, branch := range node.Children { 478 if branch.Type == CommandNode && !branch.Hidden { 479 candidates = append(candidates, branch.Name) 480 } 481 if branch.Type == CommandNode && branch.Name == token.Value { 482 c.scan.Pop() 483 c.Path = append(c.Path, &Path{ 484 Parent: node, 485 Command: branch, 486 Flags: branch.Flags, 487 }) 488 return c.trace(branch) 489 } 490 } 491 492 // Finally, check arguments. 493 for _, branch := range node.Children { 494 if branch.Type == ArgumentNode { 495 arg := branch.Argument 496 if err := arg.Parse(c.scan, c.getValue(arg)); err == nil { 497 c.Path = append(c.Path, &Path{ 498 Parent: node, 499 Argument: branch, 500 Flags: branch.Flags, 501 }) 502 return c.trace(branch) 503 } 504 } 505 } 506 507 // If there is a default command that allows args and nothing else 508 // matches, take the branch of the default command 509 if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { 510 c.Path = append(c.Path, &Path{ 511 Parent: node, 512 Command: node.DefaultCmd, 513 Flags: node.DefaultCmd.Flags, 514 }) 515 return c.trace(node.DefaultCmd) 516 } 517 518 return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token) 519 default: 520 return fmt.Errorf("unexpected token %s", token) 521 } 522 } 523 return c.maybeSelectDefault(flags, node) 524 } 525 526 // End of the line, check for a default command, but only if we're not displaying help, 527 // otherwise we'd only ever display the help for the default command. 528 func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error { 529 for _, flag := range flags { 530 if flag.Name == "help" && flag.Set { 531 return nil 532 } 533 } 534 if node.DefaultCmd != nil { 535 c.Path = append(c.Path, &Path{ 536 Parent: node.DefaultCmd, 537 Command: node.DefaultCmd, 538 Flags: node.DefaultCmd.Flags, 539 }) 540 } 541 return nil 542 } 543 544 // Resolve walks through the traced path, applying resolvers to any unset flags. 545 func (c *Context) Resolve() error { 546 resolvers := c.combineResolvers() 547 if len(resolvers) == 0 { 548 return nil 549 } 550 551 inserted := []*Path{} 552 for _, path := range c.Path { 553 for _, flag := range path.Flags { 554 // Flag has already been set on the command-line. 555 if _, ok := c.values[flag.Value]; ok { 556 continue 557 } 558 559 // Pick the last resolved value. 560 var selected interface{} 561 for _, resolver := range resolvers { 562 s, err := resolver.Resolve(c, path, flag) 563 if err != nil { 564 return fmt.Errorf("%s: %w", flag.ShortSummary(), err) 565 } 566 if s == nil { 567 continue 568 } 569 selected = s 570 } 571 572 if selected == nil { 573 continue 574 } 575 576 scan := Scan().PushTyped(selected, FlagValueToken) 577 delete(c.values, flag.Value) 578 err := flag.Parse(scan, c.getValue(flag.Value)) 579 if err != nil { 580 return err 581 } 582 inserted = append(inserted, &Path{ 583 Flag: flag, 584 Resolved: true, 585 }) 586 } 587 } 588 c.Path = append(c.Path, inserted...) 589 return nil 590 } 591 592 // Combine application-level resolvers and context resolvers. 593 func (c *Context) combineResolvers() []Resolver { 594 resolvers := []Resolver{} 595 resolvers = append(resolvers, c.Kong.resolvers...) 596 resolvers = append(resolvers, c.resolvers...) 597 return resolvers 598 } 599 600 func (c *Context) getValue(value *Value) reflect.Value { 601 v, ok := c.values[value] 602 if !ok { 603 v = reflect.New(value.Target.Type()).Elem() 604 switch v.Kind() { 605 case reflect.Ptr: 606 v.Set(reflect.New(v.Type().Elem())) 607 case reflect.Slice: 608 v.Set(reflect.MakeSlice(v.Type(), 0, 0)) 609 case reflect.Map: 610 v.Set(reflect.MakeMap(v.Type())) 611 default: 612 } 613 c.values[value] = v 614 } 615 return v 616 } 617 618 // ApplyDefaults if they are not already set. 619 func (c *Context) ApplyDefaults() error { 620 return Visit(c.Model.Node, func(node Visitable, next Next) error { 621 var value *Value 622 switch node := node.(type) { 623 case *Flag: 624 value = node.Value 625 case *Node: 626 value = node.Argument 627 case *Value: 628 value = node 629 default: 630 } 631 if value != nil { 632 if err := value.ApplyDefault(); err != nil { 633 return err 634 } 635 } 636 return next(nil) 637 }) 638 } 639 640 // Apply traced context to the target grammar. 641 func (c *Context) Apply() (string, error) { 642 path := []string{} 643 644 for _, trace := range c.Path { 645 var value *Value 646 switch { 647 case trace.App != nil: 648 case trace.Argument != nil: 649 path = append(path, "<"+trace.Argument.Name+">") 650 value = trace.Argument.Argument 651 case trace.Command != nil: 652 path = append(path, trace.Command.Name) 653 case trace.Flag != nil: 654 value = trace.Flag.Value 655 case trace.Positional != nil: 656 path = append(path, "<"+trace.Positional.Name+">") 657 value = trace.Positional 658 default: 659 panic("unsupported path ?!") 660 } 661 if value != nil { 662 value.Apply(c.getValue(value)) 663 } 664 } 665 666 return strings.Join(path, " "), nil 667 } 668 669 func flipBoolValue(value reflect.Value) error { 670 if value.Kind() == reflect.Bool { 671 value.SetBool(!value.Bool()) 672 return nil 673 } 674 675 if value.Kind() == reflect.Ptr { 676 if !value.IsNil() { 677 return flipBoolValue(value.Elem()) 678 } 679 return nil 680 } 681 682 return fmt.Errorf("cannot negate a value of %s", value.Type().String()) 683 } 684 685 func (c *Context) parseFlag(flags []*Flag, match string) (err error) { 686 candidates := []string{} 687 688 for _, flag := range flags { 689 long := "--" + flag.Name 690 matched := long == match 691 candidates = append(candidates, long) 692 if flag.Short != 0 { 693 short := "-" + string(flag.Short) 694 matched = matched || (short == match) 695 candidates = append(candidates, short) 696 } 697 for _, alias := range flag.Aliases { 698 alias = "--" + alias 699 matched = matched || (alias == match) 700 candidates = append(candidates, alias) 701 } 702 703 neg := "--no-" + flag.Name 704 if !matched && !(match == neg && flag.Tag.Negatable) { 705 continue 706 } 707 // Found a matching flag. 708 c.scan.Pop() 709 if match == neg && flag.Tag.Negatable { 710 flag.Negated = true 711 } 712 err := flag.Parse(c.scan, c.getValue(flag.Value)) 713 if err != nil { 714 var expected *expectedError 715 if errors.As(err, &expected) && expected.token.InferredType().IsAny(FlagToken, ShortFlagToken) { 716 return fmt.Errorf("%s; perhaps try %s=%q?", err.Error(), flag.ShortSummary(), expected.token) 717 } 718 return err 719 } 720 if flag.Negated { 721 value := c.getValue(flag.Value) 722 err := flipBoolValue(value) 723 if err != nil { 724 return err 725 } 726 flag.Value.Apply(value) 727 } 728 c.Path = append(c.Path, &Path{Flag: flag}) 729 return nil 730 } 731 return findPotentialCandidates(match, candidates, "unknown flag %s", match) 732 } 733 734 // Call an arbitrary function filling arguments with bound values. 735 func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) { 736 fv := reflect.ValueOf(fn) 737 bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) //nolint:govet 738 return callAnyFunction(fv, bindings) 739 } 740 741 // RunNode calls the Run() method on an arbitrary node. 742 // 743 // This is useful in conjunction with Visit(), for dynamically running commands. 744 // 745 // Any passed values will be bindable to arguments of the target Run() method. Additionally, 746 // all parent nodes in the command structure will be bound. 747 func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) { 748 type targetMethod struct { 749 node *Node 750 method reflect.Value 751 binds bindings 752 } 753 methodBinds := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) 754 methods := []targetMethod{} 755 for i := 0; node != nil; i, node = i+1, node.Parent { 756 method := getMethod(node.Target, "Run") 757 methodBinds = methodBinds.clone() 758 for p := node; p != nil; p = p.Parent { 759 methodBinds = methodBinds.add(p.Target.Addr().Interface()) 760 } 761 if method.IsValid() { 762 methods = append(methods, targetMethod{node, method, methodBinds}) 763 } 764 } 765 if len(methods) == 0 { 766 return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary()) 767 } 768 _, err = c.Apply() 769 if err != nil { 770 return err 771 } 772 773 for _, method := range methods { 774 if err = callFunction(method.method, method.binds); err != nil { 775 return err 776 } 777 } 778 return nil 779 } 780 781 // Run executes the Run() method on the selected command, which must exist. 782 // 783 // Any passed values will be bindable to arguments of the target Run() method. Additionally, 784 // all parent nodes in the command structure will be bound. 785 func (c *Context) Run(binds ...interface{}) (err error) { 786 node := c.Selected() 787 if node == nil { 788 if len(c.Path) > 0 { 789 selected := c.Path[0].Node() 790 if selected.Type == ApplicationNode { 791 method := getMethod(selected.Target, "Run") 792 if method.IsValid() { 793 return c.RunNode(selected, binds...) 794 } 795 } 796 } 797 return fmt.Errorf("no command selected") 798 } 799 return c.RunNode(node, binds...) 800 } 801 802 // PrintUsage to Kong's stdout. 803 // 804 // If summary is true, a summarised version of the help will be output. 805 func (c *Context) PrintUsage(summary bool) error { 806 options := c.helpOptions 807 options.Summary = summary 808 return c.help(options, c) 809 } 810 811 func checkMissingFlags(flags []*Flag) error { 812 xorGroupSet := map[string]bool{} 813 xorGroup := map[string][]string{} 814 missing := []string{} 815 for _, flag := range flags { 816 if flag.Set { 817 for _, xor := range flag.Xor { 818 xorGroupSet[xor] = true 819 } 820 } 821 if !flag.Required || flag.Set { 822 continue 823 } 824 if len(flag.Xor) > 0 { 825 for _, xor := range flag.Xor { 826 if xorGroupSet[xor] { 827 continue 828 } 829 xorGroup[xor] = append(xorGroup[xor], flag.Summary()) 830 } 831 } else { 832 missing = append(missing, flag.Summary()) 833 } 834 } 835 for xor, flags := range xorGroup { 836 if !xorGroupSet[xor] && len(flags) > 1 { 837 missing = append(missing, strings.Join(flags, " or ")) 838 } 839 } 840 841 if len(missing) == 0 { 842 return nil 843 } 844 845 sort.Strings(missing) 846 847 return fmt.Errorf("missing flags: %s", strings.Join(missing, ", ")) 848 } 849 850 func checkMissingChildren(node *Node) error { 851 missing := []string{} 852 853 missingArgs := []string{} 854 for _, arg := range node.Positional { 855 if arg.Required && !arg.Set { 856 missingArgs = append(missingArgs, arg.Summary()) 857 } 858 } 859 if len(missingArgs) > 0 { 860 missing = append(missing, strconv.Quote(strings.Join(missingArgs, " "))) 861 } 862 863 for _, child := range node.Children { 864 if child.Hidden { 865 continue 866 } 867 if child.Argument != nil { 868 if !child.Argument.Required { 869 continue 870 } 871 missing = append(missing, strconv.Quote(child.Summary())) 872 } else { 873 missing = append(missing, strconv.Quote(child.Name)) 874 } 875 } 876 if len(missing) == 0 { 877 return nil 878 } 879 880 if len(missing) > 5 { 881 missing = append(missing[:5], "...") 882 } 883 if len(missing) == 1 { 884 return fmt.Errorf("expected %s", missing[0]) 885 } 886 return fmt.Errorf("expected one of %s", strings.Join(missing, ", ")) 887 } 888 889 // If we're missing any positionals and they're required, return an error. 890 func checkMissingPositionals(positional int, values []*Value) error { 891 // All the positionals are in. 892 if positional >= len(values) { 893 return nil 894 } 895 896 // We're low on supplied positionals, but the missing one is optional. 897 if !values[positional].Required { 898 return nil 899 } 900 901 missing := []string{} 902 for ; positional < len(values); positional++ { 903 arg := values[positional] 904 // TODO(aat): Fix hardcoding of these env checks all over the place :\ 905 if len(arg.Tag.Envs) != 0 { 906 if atLeastOneEnvSet(arg.Tag.Envs) { 907 continue 908 } 909 } 910 missing = append(missing, "<"+arg.Name+">") 911 } 912 if len(missing) == 0 { 913 return nil 914 } 915 return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " ")) 916 } 917 918 func checkEnum(value *Value, target reflect.Value) error { 919 switch target.Kind() { 920 case reflect.Slice, reflect.Array: 921 for i := 0; i < target.Len(); i++ { 922 if err := checkEnum(value, target.Index(i)); err != nil { 923 return err 924 } 925 } 926 return nil 927 928 case reflect.Map, reflect.Struct: 929 return errors.New("enum can only be applied to a slice or value") 930 931 case reflect.Ptr: 932 if target.IsNil() { 933 return nil 934 } 935 return checkEnum(value, target.Elem()) 936 default: 937 enumSlice := value.EnumSlice() 938 v := fmt.Sprintf("%v", target) 939 enums := []string{} 940 for _, enum := range enumSlice { 941 if enum == v { 942 return nil 943 } 944 enums = append(enums, fmt.Sprintf("%q", enum)) 945 } 946 return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface()) 947 } 948 } 949 950 func checkPassthroughArg(target reflect.Value) bool { 951 typ := target.Type() 952 switch typ.Kind() { 953 case reflect.Slice: 954 return typ.Elem().Kind() == reflect.String 955 default: 956 return false 957 } 958 } 959 960 func checkXorDuplicates(paths []*Path) error { 961 for _, path := range paths { 962 seen := map[string]*Flag{} 963 for _, flag := range path.Flags { 964 if !flag.Set { 965 continue 966 } 967 for _, xor := range flag.Xor { 968 if seen[xor] != nil { 969 return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name) 970 } 971 seen[xor] = flag 972 } 973 } 974 } 975 return nil 976 } 977 978 func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error { 979 if len(haystack) == 0 { 980 return fmt.Errorf(format, args...) 981 } 982 closestCandidates := []string{} 983 for _, candidate := range haystack { 984 if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 { 985 closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate)) 986 } 987 } 988 prefix := fmt.Sprintf(format, args...) 989 if len(closestCandidates) == 1 { 990 return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0]) 991 } else if len(closestCandidates) > 1 { 992 return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", ")) 993 } 994 return fmt.Errorf("%s", prefix) 995 } 996 997 type validatable interface{ Validate() error } 998 999 func isValidatable(v reflect.Value) validatable { 1000 if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() { 1001 return nil 1002 } 1003 if validate, ok := v.Interface().(validatable); ok { 1004 return validate 1005 } 1006 if v.CanAddr() { 1007 return isValidatable(v.Addr()) 1008 } 1009 return nil 1010 } 1011 1012 func atLeastOneEnvSet(envs []string) bool { 1013 for _, env := range envs { 1014 if _, ok := os.LookupEnv(env); ok { 1015 return true 1016 } 1017 } 1018 return false 1019 }