parse-args.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package cli
  3. import (
  4. "fmt"
  5. "strings"
  6. )
  7. var _ = fmt.Print
  8. func (self *Command) parse_args(ctx *Context, args []string) error {
  9. args_to_parse := make([]string, len(args))
  10. copy(args_to_parse, args)
  11. ctx.SeenCommands = append(ctx.SeenCommands, self)
  12. if self.IgnoreAllArgs {
  13. self.Args = args
  14. return nil
  15. }
  16. var expecting_arg_for *Option
  17. options_allowed := true
  18. consume_arg := func() string { ans := args_to_parse[0]; args_to_parse = args_to_parse[1:]; return ans }
  19. handle_option := func(opt_str string, has_val bool, opt_val string, val_not_allowed bool) error {
  20. possible_options := self.FindOptions(opt_str)
  21. var opt *Option
  22. if len(possible_options) == 1 {
  23. opt = possible_options[0]
  24. opt_str = opt.MatchingAlias(NormalizeOptionName(opt_str), !strings.HasPrefix(opt_str, "--"))
  25. } else if len(possible_options) == 0 {
  26. possibles := self.SuggestionsForOption(opt_str, 2)
  27. if len(possibles) > 0 {
  28. return &ParseError{Message: fmt.Sprintf("Unknown option: :yellow:`%s`. Did you mean:\n\t%s", opt_str, strings.Join(possibles, "\n\t"))}
  29. }
  30. return &ParseError{Message: fmt.Sprintf("Unknown option: :yellow:`%s`", opt_str)}
  31. } else {
  32. ambi := make([]string, len(possible_options))
  33. for i, o := range possible_options {
  34. ambi[i] = o.MatchingAlias(NormalizeOptionName(opt_str), !strings.HasPrefix(opt_str, "--"))
  35. if ambi[i] == opt_str {
  36. opt = o
  37. break
  38. }
  39. }
  40. if opt == nil {
  41. return &ParseError{Message: fmt.Sprintf("Ambiguous option: :yellow:`%s` could be any of: %s", opt_str, strings.Join(ambi, ", "))}
  42. }
  43. }
  44. opt.seen_option = opt_str
  45. needs_arg := opt.needs_argument()
  46. if needs_arg && val_not_allowed {
  47. return &ParseError{Message: fmt.Sprintf("The option : :yellow:`%s` must be followed by a value not another option", opt_str)}
  48. }
  49. if has_val {
  50. if !needs_arg {
  51. return &ParseError{Message: fmt.Sprintf("The option: :yellow:`%s` does not take values", opt_str)}
  52. }
  53. return opt.add_value(opt_val)
  54. } else if needs_arg {
  55. expecting_arg_for = opt
  56. } else {
  57. opt.add_value("")
  58. }
  59. return nil
  60. }
  61. for len(args_to_parse) > 0 {
  62. arg := consume_arg()
  63. if expecting_arg_for == nil {
  64. if options_allowed && strings.HasPrefix(arg, "-") && arg != "-" {
  65. // handle option arg
  66. if arg == "--" {
  67. options_allowed = false
  68. continue
  69. }
  70. opt_str := arg
  71. opt_val := ""
  72. has_val := false
  73. if strings.HasPrefix(opt_str, "--") {
  74. parts := strings.SplitN(arg, "=", 2)
  75. if len(parts) > 1 {
  76. has_val = true
  77. opt_val = parts[1]
  78. }
  79. opt_str = parts[0]
  80. err := handle_option(opt_str, has_val, opt_val, false)
  81. if err != nil {
  82. return err
  83. }
  84. } else {
  85. runes := []rune(opt_str[1:])
  86. for i, sl := range runes {
  87. err := handle_option("-"+string(sl), false, "", i < len(runes)-1)
  88. if err != nil {
  89. return err
  90. }
  91. }
  92. }
  93. } else {
  94. // handle non option arg
  95. if self.AllowOptionsAfterArgs <= len(self.Args) {
  96. options_allowed = false
  97. }
  98. if self.HasSubCommands() {
  99. possible_cmds := self.FindSubCommands(arg)
  100. if len(possible_cmds) == 1 {
  101. return possible_cmds[0].parse_args(ctx, args_to_parse)
  102. }
  103. if !self.SubCommandIsOptional {
  104. if len(possible_cmds) == 0 {
  105. possibles := self.SuggestionsForCommand(arg, 2)
  106. if len(possibles) > 0 {
  107. return &ParseError{Message: fmt.Sprintf("Unknown subcommand: :yellow:`%s`. Did you mean:\n\t%s", arg, strings.Join(possibles, "\n\t"))}
  108. }
  109. return &ParseError{Message: fmt.Sprintf(":yellow:`%s` is not a known subcommand for :emph:`%s`. Use --help to get a list of valid subcommands.", arg, self.Name)}
  110. }
  111. cn := make([]string, len(possible_cmds))
  112. for i, x := range possible_cmds {
  113. cn[i] = x.Name
  114. }
  115. return &ParseError{Message: fmt.Sprintf(
  116. ":yellow:`%s` is not a known subcommand for :emph:`%s`. Did you mean:\n\t%s", arg, self.Name, strings.Join(cn, "\n\t"))}
  117. }
  118. }
  119. self.Args = append(self.Args, arg)
  120. }
  121. } else {
  122. // handle option value
  123. err := expecting_arg_for.add_value(arg)
  124. if err != nil {
  125. return err
  126. }
  127. expecting_arg_for = nil
  128. }
  129. }
  130. return nil
  131. }