github.com/maresnic/mr-kong@v1.0.0/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 tail = strings.TrimLeft(tail, " =") 405 c.scan.PushTyped(tail, ShortFlagTailToken) 406 } 407 c.scan.PushTyped(v[1:2], ShortFlagToken) 408 } 409 default: 410 c.scan.Pop() 411 c.scan.PushTyped(token.Value, PositionalArgumentToken) 412 } 413 414 case ShortFlagTailToken: 415 c.scan.Pop() 416 // Note: tokens must be pushed in reverse order. 417 if tail := token.String()[1:]; tail != "" { 418 c.scan.PushTyped(tail, ShortFlagTailToken) 419 } 420 c.scan.PushTyped(token.String()[0:1], ShortFlagToken) 421 422 case FlagToken: 423 if err := c.parseFlag(flags, token.String()); err != nil { 424 return err 425 } 426 427 case ShortFlagToken: 428 if err := c.parseFlag(flags, token.String()); err != nil { 429 return err 430 } 431 432 case FlagValueToken: 433 return fmt.Errorf("unexpected flag argument %q", token.Value) 434 435 case PositionalArgumentToken: 436 candidates := []string{} 437 438 // Ensure we've consumed all positional arguments. 439 if positional < len(node.Positional) { 440 arg := node.Positional[positional] 441 442 if arg.Passthrough { 443 c.endParsing() 444 } 445 446 arg.Active = true 447 err := arg.Parse(c.scan, c.getValue(arg)) 448 if err != nil { 449 return err 450 } 451 c.Path = append(c.Path, &Path{ 452 Parent: node, 453 Positional: arg, 454 }) 455 positional++ 456 break 457 } 458 459 // Assign token value to a branch name if tagged as an alias 460 // An alias will be ignored in the case of an existing command 461 cmds := make(map[string]bool) 462 for _, branch := range node.Children { 463 if branch.Type == CommandNode { 464 cmds[branch.Name] = true 465 } 466 } 467 for _, branch := range node.Children { 468 for _, a := range branch.Aliases { 469 _, ok := cmds[a] 470 if token.Value == a && !ok { 471 token.Value = branch.Name 472 break 473 } 474 } 475 } 476 477 // After positional arguments have been consumed, check commands next... 478 for _, branch := range node.Children { 479 if branch.Type == CommandNode && !branch.Hidden { 480 candidates = append(candidates, branch.Name) 481 } 482 if branch.Type == CommandNode && branch.Name == token.Value { 483 c.scan.Pop() 484 c.Path = append(c.Path, &Path{ 485 Parent: node, 486 Command: branch, 487 Flags: branch.Flags, 488 }) 489 return c.trace(branch) 490 } 491 } 492 493 // Finally, check arguments. 494 for _, branch := range node.Children { 495 if branch.Type == ArgumentNode { 496 arg := branch.Argument 497 if err := arg.Parse(c.scan, c.getValue(arg)); err == nil { 498 c.Path = append(c.Path, &Path{ 499 Parent: node, 500 Argument: branch, 501 Flags: branch.Flags, 502 }) 503 return c.trace(branch) 504 } 505 } 506 } 507 508 // If there is a default command that allows args and nothing else 509 // matches, take the branch of the default command 510 if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" { 511 c.Path = append(c.Path, &Path{ 512 Parent: node, 513 Command: node.DefaultCmd, 514 Flags: node.DefaultCmd.Flags, 515 }) 516 return c.trace(node.DefaultCmd) 517 } 518 519 return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token) 520 default: 521 return fmt.Errorf("unexpected token %s", token) 522 } 523 } 524 return c.maybeSelectDefault(flags, node) 525 } 526 527 // End of the line, check for a default command, but only if we're not displaying help, 528 // otherwise we'd only ever display the help for the default command. 529 func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error { 530 for _, flag := range flags { 531 if flag.Name == "help" && flag.Set { 532 return nil 533 } 534 } 535 if node.DefaultCmd != nil { 536 c.Path = append(c.Path, &Path{ 537 Parent: node.DefaultCmd, 538 Command: node.DefaultCmd, 539 Flags: node.DefaultCmd.Flags, 540 }) 541 } 542 return nil 543 } 544 545 // Resolve walks through the traced path, applying resolvers to any unset flags. 546 func (c *Context) Resolve() error { 547 resolvers := c.combineResolvers() 548 if len(resolvers) == 0 { 549 return nil 550 } 551 552 inserted := []*Path{} 553 for _, path := range c.Path { 554 for _, flag := range path.Flags { 555 // Flag has already been set on the command-line. 556 if _, ok := c.values[flag.Value]; ok { 557 continue 558 } 559 560 // Pick the last resolved value. 561 var selected interface{} 562 for _, resolver := range resolvers { 563 s, err := resolver.Resolve(c, path, flag) 564 if err != nil { 565 return fmt.Errorf("%s: %w", flag.ShortSummary(), err) 566 } 567 if s == nil { 568 continue 569 } 570 selected = s 571 } 572 573 if selected == nil { 574 continue 575 } 576 577 scan := Scan().PushTyped(selected, FlagValueToken) 578 delete(c.values, flag.Value) 579 err := flag.Parse(scan, c.getValue(flag.Value)) 580 if err != nil { 581 return err 582 } 583 inserted = append(inserted, &Path{ 584 Flag: flag, 585 Resolved: true, 586 }) 587 } 588 } 589 c.Path = append(c.Path, inserted...) 590 return nil 591 } 592 593 // Combine application-level resolvers and context resolvers. 594 func (c *Context) combineResolvers() []Resolver { 595 resolvers := []Resolver{} 596 resolvers = append(resolvers, c.Kong.resolvers...) 597 resolvers = append(resolvers, c.resolvers...) 598 return resolvers 599 } 600 601 func (c *Context) getValue(value *Value) reflect.Value { 602 v, ok := c.values[value] 603 if !ok { 604 v = reflect.New(value.Target.Type()).Elem() 605 switch v.Kind() { 606 case reflect.Ptr: 607 v.Set(reflect.New(v.Type().Elem())) 608 case reflect.Slice: 609 v.Set(reflect.MakeSlice(v.Type(), 0, 0)) 610 case reflect.Map: 611 v.Set(reflect.MakeMap(v.Type())) 612 default: 613 } 614 c.values[value] = v 615 } 616 return v 617 } 618 619 // ApplyDefaults if they are not already set. 620 func (c *Context) ApplyDefaults() error { 621 return Visit(c.Model.Node, func(node Visitable, next Next) error { 622 var value *Value 623 switch node := node.(type) { 624 case *Flag: 625 value = node.Value 626 case *Node: 627 value = node.Argument 628 case *Value: 629 value = node 630 default: 631 } 632 if value != nil { 633 if err := value.ApplyDefault(); err != nil { 634 return err 635 } 636 } 637 return next(nil) 638 }) 639 } 640 641 // Apply traced context to the target grammar. 642 func (c *Context) Apply() (string, error) { 643 path := []string{} 644 645 for _, trace := range c.Path { 646 var value *Value 647 switch { 648 case trace.App != nil: 649 case trace.Argument != nil: 650 path = append(path, "<"+trace.Argument.Name+">") 651 value = trace.Argument.Argument 652 case trace.Command != nil: 653 path = append(path, trace.Command.Name) 654 case trace.Flag != nil: 655 value = trace.Flag.Value 656 case trace.Positional != nil: 657 path = append(path, "<"+trace.Positional.Name+">") 658 value = trace.Positional 659 default: 660 panic("unsupported path ?!") 661 } 662 if value != nil { 663 value.Apply(c.getValue(value)) 664 } 665 } 666 667 return strings.Join(path, " "), nil 668 } 669 670 func flipBoolValue(value reflect.Value) error { 671 if value.Kind() == reflect.Bool { 672 value.SetBool(!value.Bool()) 673 return nil 674 } 675 676 if value.Kind() == reflect.Ptr { 677 if !value.IsNil() { 678 return flipBoolValue(value.Elem()) 679 } 680 return nil 681 } 682 683 return fmt.Errorf("cannot negate a value of %s", value.Type().String()) 684 } 685 686 func (c *Context) parseFlag(flags []*Flag, match string) (err error) { 687 candidates := []string{} 688 689 for _, flag := range flags { 690 long := "--" + flag.Name 691 matched := long == match 692 candidates = append(candidates, long) 693 if flag.Short != 0 { 694 short := "-" + string(flag.Short) 695 matched = matched || (short == match) 696 candidates = append(candidates, short) 697 } 698 for _, alias := range flag.Aliases { 699 alias = "--" + alias 700 matched = matched || (alias == match) 701 candidates = append(candidates, alias) 702 } 703 704 neg := "--no-" + flag.Name 705 if !matched && !(match == neg && flag.Tag.Negatable) { 706 continue 707 } 708 // Found a matching flag. 709 c.scan.Pop() 710 if match == neg && flag.Tag.Negatable { 711 flag.Negated = true 712 } 713 err := flag.Parse(c.scan, c.getValue(flag.Value)) 714 if err != nil { 715 var expected *expectedError 716 if errors.As(err, &expected) && expected.token.InferredType().IsAny(FlagToken, ShortFlagToken) { 717 return fmt.Errorf("%s; perhaps try %s=%q?", err.Error(), flag.ShortSummary(), expected.token) 718 } 719 return err 720 } 721 if flag.Negated { 722 value := c.getValue(flag.Value) 723 err := flipBoolValue(value) 724 if err != nil { 725 return err 726 } 727 flag.Value.Apply(value) 728 } 729 c.Path = append(c.Path, &Path{Flag: flag}) 730 return nil 731 } 732 return findPotentialCandidates(match, candidates, "unknown flag %s", match) 733 } 734 735 // Call an arbitrary function filling arguments with bound values. 736 func (c *Context) Call(fn any, binds ...interface{}) (out []interface{}, err error) { 737 fv := reflect.ValueOf(fn) 738 bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) //nolint:govet 739 return callAnyFunction(fv, bindings) 740 } 741 742 // RunNode calls the Run() method on an arbitrary node. 743 // 744 // This is useful in conjunction with Visit(), for dynamically running commands. 745 // 746 // Any passed values will be bindable to arguments of the target Run() method. Additionally, 747 // all parent nodes in the command structure will be bound. 748 func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) { 749 type targetMethod struct { 750 node *Node 751 method reflect.Value 752 binds bindings 753 } 754 methodBinds := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) 755 methods := []targetMethod{} 756 for i := 0; node != nil; i, node = i+1, node.Parent { 757 method := getMethod(node.Target, "Run") 758 methodBinds = methodBinds.clone() 759 for p := node; p != nil; p = p.Parent { 760 methodBinds = methodBinds.add(p.Target.Addr().Interface()) 761 } 762 if method.IsValid() { 763 methods = append(methods, targetMethod{node, method, methodBinds}) 764 } 765 } 766 if len(methods) == 0 { 767 return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary()) 768 } 769 _, err = c.Apply() 770 if err != nil { 771 return err 772 } 773 774 for _, method := range methods { 775 if err = callFunction(method.method, method.binds); err != nil { 776 return err 777 } 778 } 779 return nil 780 } 781 782 // Run executes the Run() method on the selected command, which must exist. 783 // 784 // Any passed values will be bindable to arguments of the target Run() method. Additionally, 785 // all parent nodes in the command structure will be bound. 786 func (c *Context) Run(binds ...interface{}) (err error) { 787 node := c.Selected() 788 if node == nil { 789 if len(c.Path) > 0 { 790 selected := c.Path[0].Node() 791 if selected.Type == ApplicationNode { 792 method := getMethod(selected.Target, "Run") 793 if method.IsValid() { 794 return c.RunNode(selected, binds...) 795 } 796 } 797 } 798 return fmt.Errorf("no command selected") 799 } 800 return c.RunNode(node, binds...) 801 } 802 803 // PrintUsage to Kong's stdout. 804 // 805 // If summary is true, a summarised version of the help will be output. 806 func (c *Context) PrintUsage(summary bool) error { 807 options := c.helpOptions 808 options.Summary = summary 809 return c.help(options, c) 810 } 811 812 func checkMissingFlags(flags []*Flag) error { 813 xorGroupSet := map[string]bool{} 814 xorGroup := map[string][]string{} 815 missing := []string{} 816 for _, flag := range flags { 817 if flag.Set { 818 for _, xor := range flag.Xor { 819 xorGroupSet[xor] = true 820 } 821 } 822 if !flag.Required || flag.Set { 823 continue 824 } 825 if len(flag.Xor) > 0 { 826 for _, xor := range flag.Xor { 827 if xorGroupSet[xor] { 828 continue 829 } 830 xorGroup[xor] = append(xorGroup[xor], flag.Summary()) 831 } 832 } else { 833 missing = append(missing, flag.Summary()) 834 } 835 } 836 for xor, flags := range xorGroup { 837 if !xorGroupSet[xor] && len(flags) > 1 { 838 missing = append(missing, strings.Join(flags, " or ")) 839 } 840 } 841 842 if len(missing) == 0 { 843 return nil 844 } 845 846 sort.Strings(missing) 847 848 return fmt.Errorf("missing flags: %s", strings.Join(missing, ", ")) 849 } 850 851 func checkMissingChildren(node *Node) error { 852 missing := []string{} 853 854 missingArgs := []string{} 855 for _, arg := range node.Positional { 856 if arg.Required && !arg.Set { 857 missingArgs = append(missingArgs, arg.Summary()) 858 } 859 } 860 if len(missingArgs) > 0 { 861 missing = append(missing, strconv.Quote(strings.Join(missingArgs, " "))) 862 } 863 864 for _, child := range node.Children { 865 if child.Hidden { 866 continue 867 } 868 if child.Argument != nil { 869 if !child.Argument.Required { 870 continue 871 } 872 missing = append(missing, strconv.Quote(child.Summary())) 873 } else { 874 missing = append(missing, strconv.Quote(child.Name)) 875 } 876 } 877 if len(missing) == 0 { 878 return nil 879 } 880 881 if len(missing) > 5 { 882 missing = append(missing[:5], "...") 883 } 884 if len(missing) == 1 { 885 return fmt.Errorf("expected %s", missing[0]) 886 } 887 return fmt.Errorf("expected one of %s", strings.Join(missing, ", ")) 888 } 889 890 // If we're missing any positionals and they're required, return an error. 891 func checkMissingPositionals(positional int, values []*Value) error { 892 // All the positionals are in. 893 if positional >= len(values) { 894 return nil 895 } 896 897 // We're low on supplied positionals, but the missing one is optional. 898 if !values[positional].Required { 899 return nil 900 } 901 902 missing := []string{} 903 for ; positional < len(values); positional++ { 904 arg := values[positional] 905 // TODO(aat): Fix hardcoding of these env checks all over the place :\ 906 if len(arg.Tag.Envs) != 0 { 907 if atLeastOneEnvSet(arg.Tag.Envs) { 908 continue 909 } 910 } 911 missing = append(missing, "<"+arg.Name+">") 912 } 913 if len(missing) == 0 { 914 return nil 915 } 916 return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " ")) 917 } 918 919 func checkEnum(value *Value, target reflect.Value) error { 920 switch target.Kind() { 921 case reflect.Slice, reflect.Array: 922 for i := 0; i < target.Len(); i++ { 923 if err := checkEnum(value, target.Index(i)); err != nil { 924 return err 925 } 926 } 927 return nil 928 929 case reflect.Map, reflect.Struct: 930 return errors.New("enum can only be applied to a slice or value") 931 932 case reflect.Ptr: 933 if target.IsNil() { 934 return nil 935 } 936 return checkEnum(value, target.Elem()) 937 default: 938 enumSlice := value.EnumSlice() 939 v := fmt.Sprintf("%v", target) 940 enums := []string{} 941 for _, enum := range enumSlice { 942 if enum == v { 943 return nil 944 } 945 enums = append(enums, fmt.Sprintf("%q", enum)) 946 } 947 return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface()) 948 } 949 } 950 951 func checkPassthroughArg(target reflect.Value) bool { 952 typ := target.Type() 953 switch typ.Kind() { 954 case reflect.Slice: 955 return typ.Elem().Kind() == reflect.String 956 default: 957 return false 958 } 959 } 960 961 func checkXorDuplicates(paths []*Path) error { 962 for _, path := range paths { 963 seen := map[string]*Flag{} 964 for _, flag := range path.Flags { 965 if !flag.Set { 966 continue 967 } 968 for _, xor := range flag.Xor { 969 if seen[xor] != nil { 970 return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name) 971 } 972 seen[xor] = flag 973 } 974 } 975 } 976 return nil 977 } 978 979 func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error { 980 if len(haystack) == 0 { 981 return fmt.Errorf(format, args...) 982 } 983 closestCandidates := []string{} 984 for _, candidate := range haystack { 985 if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 { 986 closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate)) 987 } 988 } 989 prefix := fmt.Sprintf(format, args...) 990 if len(closestCandidates) == 1 { 991 return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0]) 992 } else if len(closestCandidates) > 1 { 993 return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", ")) 994 } 995 return fmt.Errorf("%s", prefix) 996 } 997 998 type validatable interface{ Validate() error } 999 1000 func isValidatable(v reflect.Value) validatable { 1001 if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() { 1002 return nil 1003 } 1004 if validate, ok := v.Interface().(validatable); ok { 1005 return validate 1006 } 1007 if v.CanAddr() { 1008 return isValidatable(v.Addr()) 1009 } 1010 return nil 1011 } 1012 1013 func atLeastOneEnvSet(envs []string) bool { 1014 for _, env := range envs { 1015 if _, ok := os.LookupEnv(env); ok { 1016 return true 1017 } 1018 } 1019 return false 1020 }