option.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package cli
  3. import (
  4. "fmt"
  5. "slices"
  6. "strconv"
  7. "strings"
  8. )
  9. var _ = fmt.Print
  10. type OptionType int
  11. const (
  12. StringOption OptionType = iota
  13. IntegerOption
  14. FloatOption
  15. BoolOption
  16. CountOption
  17. )
  18. type Alias struct {
  19. NameWithoutHyphens string
  20. IsShort bool
  21. IsUnset bool
  22. }
  23. func (self *Alias) String() string {
  24. if self.IsShort {
  25. return "-" + self.NameWithoutHyphens
  26. }
  27. return "--" + self.NameWithoutHyphens
  28. }
  29. type OptionSpec struct {
  30. Name string
  31. Type string
  32. Dest string
  33. Choices string
  34. Depth int
  35. Default string
  36. Help string
  37. Completer CompletionFunc
  38. }
  39. type Option struct {
  40. Name string
  41. Aliases []Alias
  42. Choices []string
  43. Default string
  44. OptionType OptionType
  45. Hidden bool
  46. Depth int
  47. Help string
  48. IsList bool
  49. Parent *Command
  50. Completer CompletionFunc
  51. values_from_cmdline []string
  52. parsed_values_from_cmdline []any
  53. parsed_default any
  54. seen_option string
  55. }
  56. func (self *Option) reset() {
  57. self.values_from_cmdline = self.values_from_cmdline[:0]
  58. self.parsed_values_from_cmdline = self.parsed_values_from_cmdline[:0]
  59. self.seen_option = ""
  60. }
  61. func (self *Option) needs_argument() bool {
  62. return self.OptionType != BoolOption && self.OptionType != CountOption
  63. }
  64. func (self *Option) MatchingAlias(prefix_without_hyphens string, is_short bool) string {
  65. for _, a := range self.Aliases {
  66. if a.IsShort == is_short && strings.HasPrefix(a.NameWithoutHyphens, prefix_without_hyphens) {
  67. return a.String()
  68. }
  69. }
  70. return ""
  71. }
  72. func (self *Option) HasAlias(name_without_hyphens string, is_short bool) bool {
  73. for _, a := range self.Aliases {
  74. if a.IsShort == is_short && a.NameWithoutHyphens == name_without_hyphens {
  75. return true
  76. }
  77. }
  78. return false
  79. }
  80. type ParseError struct {
  81. Option *Option
  82. Message string
  83. }
  84. func (self *ParseError) Error() string { return self.Message }
  85. func NormalizeOptionName(name string) string {
  86. return strings.ReplaceAll(strings.TrimLeft(name, "-"), "_", "-")
  87. }
  88. func (self *Option) parsed_value() any {
  89. if len(self.values_from_cmdline) == 0 {
  90. return self.parsed_default
  91. }
  92. switch self.OptionType {
  93. case CountOption:
  94. return len(self.parsed_values_from_cmdline)
  95. case StringOption:
  96. if self.IsList {
  97. ans := make([]string, len(self.parsed_values_from_cmdline))
  98. for i, x := range self.parsed_values_from_cmdline {
  99. ans[i] = x.(string)
  100. }
  101. return ans
  102. }
  103. fallthrough
  104. default:
  105. return self.parsed_values_from_cmdline[len(self.parsed_values_from_cmdline)-1]
  106. }
  107. }
  108. func (self *Option) parse_value(val string) (any, error) {
  109. switch self.OptionType {
  110. case BoolOption:
  111. switch val {
  112. case "true":
  113. return true, nil
  114. case "false":
  115. return false, nil
  116. default:
  117. return nil, &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`.", val, self.seen_option)}
  118. }
  119. case StringOption:
  120. return val, nil
  121. case IntegerOption, CountOption:
  122. pval, err := strconv.ParseInt(val, 0, 0)
  123. if err != nil {
  124. return nil, &ParseError{Option: self, Message: fmt.Sprintf(
  125. ":yellow:`%s` is not a valid number for :bold:`%s`. Only integers in decimal, hexadecimal, binary or octal notation are accepted.", val, self.seen_option)}
  126. }
  127. return int(pval), nil
  128. case FloatOption:
  129. pval, err := strconv.ParseFloat(val, 64)
  130. if err != nil {
  131. return nil, &ParseError{Option: self, Message: fmt.Sprintf(
  132. ":yellow:`%s` is not a valid number for :bold:`%s`. Only floats in decimal and hexadecimal notation are accepted.", val, self.seen_option)}
  133. }
  134. return pval, nil
  135. default:
  136. return nil, &ParseError{Option: self, Message: fmt.Sprintf("Unknown option type for %s", self.Name)}
  137. }
  138. }
  139. func (self *Option) add_value(val string) error {
  140. name_without_hyphens := NormalizeOptionName(self.seen_option)
  141. switch self.OptionType {
  142. case BoolOption:
  143. for _, x := range self.Aliases {
  144. if x.NameWithoutHyphens == name_without_hyphens {
  145. if x.IsUnset {
  146. self.values_from_cmdline = append(self.values_from_cmdline, "false")
  147. self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, false)
  148. } else {
  149. self.values_from_cmdline = append(self.values_from_cmdline, "true")
  150. self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, true)
  151. }
  152. return nil
  153. }
  154. }
  155. case StringOption:
  156. if self.Choices != nil && !slices.Contains(self.Choices, val) {
  157. return &ParseError{Option: self, Message: fmt.Sprintf(":yellow:`%s` is not a valid value for :bold:`%s`. Valid values: %s",
  158. val, self.seen_option, strings.Join(self.Choices, ", "),
  159. )}
  160. }
  161. self.values_from_cmdline = append(self.values_from_cmdline, val)
  162. self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, val)
  163. case IntegerOption, FloatOption:
  164. pval, err := self.parse_value(val)
  165. if err != nil {
  166. return err
  167. }
  168. self.values_from_cmdline = append(self.values_from_cmdline, val)
  169. self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, pval)
  170. case CountOption:
  171. self.values_from_cmdline = append(self.values_from_cmdline, val)
  172. self.parsed_values_from_cmdline = append(self.parsed_values_from_cmdline, 1)
  173. }
  174. return nil
  175. }