argv.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package argv
  2. import (
  3. "fmt"
  4. "errors"
  5. "reflect"
  6. "strings"
  7. "strconv"
  8. )
  9. func ParseArgs[T any] (args ([] string)) (T, string, error) {
  10. var struct_ T
  11. var help string
  12. var err = parseArgs(args, &help, reflect.ValueOf(&struct_))
  13. return struct_, help, err
  14. }
  15. func parseArgs(args ([] string), help *string, ptr reflect.Value) error {
  16. if ptr.Kind() != reflect.Ptr {
  17. panic("invalid argument: expect a pointer to write data")
  18. }
  19. if ptr.Elem().Kind() != reflect.Struct {
  20. panic("invalid argument: expect a struct pointer to write fields")
  21. }
  22. const key_prefix = "--"
  23. const key_val_sep = '='
  24. var positional = make([] string, 0)
  25. var named = make(map[string] string)
  26. var named_used = make(map[string] bool)
  27. var no_more_named = false
  28. var arg0 = args[0]
  29. for _, arg := range args[1:] {
  30. if arg == key_prefix {
  31. no_more_named = true
  32. } else if !(no_more_named) && strings.HasPrefix(arg, key_prefix) {
  33. var arg = strings.TrimPrefix(arg, key_prefix)
  34. var n = strings.IndexRune(arg, key_val_sep)
  35. var key, value = (func() (string, string) {
  36. if n == -1 {
  37. return arg, ""
  38. } else {
  39. return arg[:n], arg[(n + 1):]
  40. }
  41. })()
  42. named[key] = value
  43. } else {
  44. positional = append(positional, arg)
  45. }
  46. }
  47. var positional_hint string
  48. var named_hint = make(map[string] string)
  49. var named_desc = make(map[string] string)
  50. var commands ([] string)
  51. var arity = make(map[string] int)
  52. var default_command string
  53. var options = make([] string, 0)
  54. var obj = ptr.Elem()
  55. var t = obj.Type()
  56. var first_err = error(nil)
  57. for i := 0; i < t.NumField(); i += 1 {
  58. var field = t.Field(i)
  59. var kind = field.Tag.Get("arg")
  60. var value, err = (func() (interface{}, error) {
  61. switch kind {
  62. case "positional":
  63. positional_hint = field.Tag.Get("hint")
  64. return ([] string)(positional), nil
  65. case "command":
  66. const sep = "; "
  67. var key = strings.Split(field.Tag.Get("key"), sep)
  68. var desc = strings.Split(field.Tag.Get("desc"), sep)
  69. for i := range key {
  70. var parts = strings.Split(key[i], "-")
  71. if len(parts) >= 2 {
  72. key[i] = parts[0]
  73. var n, _ = strconv.Atoi(parts[1])
  74. arity[key[i]] = n
  75. } else {
  76. arity[key[i]] = -1
  77. }
  78. }
  79. commands = key
  80. default_command = field.Tag.Get("default")
  81. for i := range key {
  82. if i < len(desc) {
  83. named_desc[key[i]] = desc[i]
  84. }
  85. }
  86. var command = ""
  87. for _, item := range key {
  88. var _, has_item = named[item]
  89. if has_item {
  90. if command == "" {
  91. command = item
  92. named_used[item] = true
  93. } else {
  94. return nil, errors.New(fmt.Sprintf(
  95. "ambiguous command: '%s' or '%s' ?",
  96. command, item,
  97. ))
  98. }
  99. }
  100. }
  101. if command == "" {
  102. command = default_command
  103. }
  104. var command_arity = arity[command]
  105. if command_arity == 0 {
  106. if len(positional) > 0 {
  107. return nil, errors.New(fmt.Sprintf(
  108. "redundant argument(s) for '%s' command",
  109. command,
  110. ))
  111. }
  112. } else if command_arity > 0 {
  113. if len(positional) < command_arity {
  114. return nil, errors.New(fmt.Sprintf(
  115. "not enough arguments for '%s' command",
  116. command,
  117. ))
  118. }
  119. }
  120. return string(command), nil
  121. case "flag-enable":
  122. var key = field.Tag.Get("key")
  123. options = append(options, key)
  124. named_desc[key] = field.Tag.Get("desc")
  125. var _, flag_is_set = named[key]
  126. var enabled = flag_is_set
  127. named_used[key] = true
  128. return bool(enabled), nil
  129. case "flag-disable":
  130. var key = field.Tag.Get("key")
  131. options = append(options, key)
  132. named_desc[key] = field.Tag.Get("desc")
  133. var _, flag_is_set = named[key]
  134. var enabled = !(flag_is_set)
  135. named_used[key] = true
  136. return bool(enabled), nil
  137. case "value-string":
  138. var key = field.Tag.Get("key")
  139. options = append(options, key)
  140. named_hint[key] = field.Tag.Get("hint")
  141. named_desc[key] = field.Tag.Get("desc")
  142. named_used[key] = true
  143. return string(named[key]), nil
  144. default:
  145. return nil, errors.New("invalid argument kind: " + kind)
  146. }
  147. })()
  148. if err == nil {
  149. obj.Field(i).Set(reflect.ValueOf(value))
  150. } else {
  151. if first_err == nil {
  152. first_err = err
  153. }
  154. }
  155. }
  156. if first_err == nil {
  157. for key, _ := range named {
  158. if !(named_used[key]) {
  159. first_err = errors.New("unknown option: " + key)
  160. break
  161. }
  162. }
  163. }
  164. var buf strings.Builder
  165. var cmd_opt_hint = "[COMMAND|OPTION]..."
  166. if positional_hint == "" {
  167. positional_hint = "[ARGUMENT]..."
  168. }
  169. fmt.Fprintf(&buf, "Usage: %s %s %s\n", arg0, cmd_opt_hint, positional_hint)
  170. const pad1 = " "
  171. const pad2 = " \t"
  172. if len(commands) > 0 {
  173. buf.WriteRune('\n')
  174. buf.WriteString("Commands:")
  175. buf.WriteRune('\n')
  176. for _, item := range commands {
  177. var desc = named_desc[item]
  178. buf.WriteString(pad1 + key_prefix + item)
  179. if item == default_command {
  180. buf.WriteString(pad2 + "(default) " + desc)
  181. } else {
  182. buf.WriteString(pad2 + desc)
  183. }
  184. buf.WriteRune('\n')
  185. }
  186. }
  187. if len(options) > 0 {
  188. buf.WriteRune('\n')
  189. buf.WriteString("Options:")
  190. buf.WriteRune('\n')
  191. for _, item := range options {
  192. var hint = named_hint[item]
  193. var desc = named_desc[item]
  194. if hint != "" {
  195. var key_val_sep = string([] rune { key_val_sep })
  196. buf.WriteString(pad1 + key_prefix + item + key_val_sep + hint)
  197. } else {
  198. buf.WriteString(pad1 + key_prefix + item)
  199. }
  200. buf.WriteString(pad2 + desc)
  201. buf.WriteRune('\n')
  202. }
  203. }
  204. *help = buf.String()
  205. return first_err
  206. }