option.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package cli
  2. import (
  3. "encoding/json"
  4. "flag"
  5. "os"
  6. "reflect"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "vultras.su/core/pwa/errors"
  11. )
  12. type optionParser struct {
  13. flags *flag.FlagSet
  14. options []option
  15. }
  16. func (p *optionParser) parse(v interface{}) ([]option, error) {
  17. p.options = nil
  18. if v == nil {
  19. return nil, nil
  20. }
  21. val := reflect.ValueOf(v)
  22. if val.Kind() != reflect.Ptr {
  23. return nil, errors.New("receiver is not a pointer").
  24. WithTag("type", val.Type()).
  25. WithTag("kind", val.Kind())
  26. }
  27. if val = val.Elem(); val.Kind() != reflect.Struct {
  28. return nil, errors.New("receiver does not point to a struct").
  29. WithTag("type", val.Type()).
  30. WithTag("kind", val.Kind())
  31. }
  32. p.parseStruct("", val)
  33. for _, o := range p.options {
  34. if o.name != "h" && o.name != "help" {
  35. p.flags.Var(o, o.name, o.help)
  36. }
  37. }
  38. return p.options, nil
  39. }
  40. func (p *optionParser) parseStruct(prefix string, v reflect.Value) {
  41. for i := 0; i < v.NumField(); i++ {
  42. fval := v.Field(i)
  43. if !fval.CanSet() {
  44. continue
  45. }
  46. finfo := v.Type().Field(i)
  47. fname := finfo.Tag.Get("cli")
  48. if fname == "" {
  49. fname = finfo.Name
  50. }
  51. fname = normalizeCLIOptionName(fname)
  52. if prefix != "" {
  53. fname = prefix + "." + fname
  54. }
  55. envKey := finfo.Tag.Get("env")
  56. if envKey == "" {
  57. envKey = normalizeEnvOptionName(fname)
  58. }
  59. o := option{
  60. name: fname,
  61. help: finfo.Tag.Get("help"),
  62. envKey: envKey,
  63. value: fval,
  64. }
  65. if envVal, ok := os.LookupEnv(envKey); ok && envKey != "-" {
  66. o.Set(envVal)
  67. }
  68. p.options = append(p.options, o)
  69. if fval.Kind() == reflect.Struct {
  70. p.parseStruct(fname, fval)
  71. }
  72. }
  73. }
  74. type option struct {
  75. name string
  76. help string
  77. envKey string
  78. value reflect.Value
  79. }
  80. func (o option) IsBoolFlag() bool {
  81. return o.value.Kind() == reflect.Bool
  82. }
  83. func (o option) String() string {
  84. switch o.value.Kind() {
  85. case reflect.String:
  86. return o.value.String()
  87. }
  88. switch value := o.value.Interface().(type) {
  89. case time.Duration:
  90. return value.String()
  91. }
  92. b, _ := json.Marshal(o.value.Interface())
  93. return string(b)
  94. }
  95. func (o option) Set(s string) error {
  96. switch o.value.Kind() {
  97. case reflect.String:
  98. o.value.SetString(s)
  99. return nil
  100. }
  101. switch o.value.Interface().(type) {
  102. case time.Duration:
  103. return setDuration(o.value, s)
  104. case time.Time, *time.Time:
  105. s = strconv.Quote(s)
  106. }
  107. return json.Unmarshal([]byte(s), o.value.Addr().Interface())
  108. }
  109. func setDuration(v reflect.Value, s string) error {
  110. d, err := time.ParseDuration(s)
  111. if err != nil {
  112. i, err := strconv.ParseInt(s, 10, 64)
  113. if err != nil {
  114. return err
  115. }
  116. v.SetInt(i)
  117. return nil
  118. }
  119. v.SetInt(int64(d))
  120. return nil
  121. }
  122. func normalizeOptionName(name string, sep string) string {
  123. var b strings.Builder
  124. write := func(s string) {
  125. if s != "" {
  126. if b.Len() != 0 {
  127. b.WriteString(sep)
  128. }
  129. b.WriteString(strings.ToLower(s))
  130. }
  131. }
  132. start := 0
  133. end := 0
  134. for end < len(name) {
  135. switch {
  136. case isUpperCase(name[end]) && end > 0 && !isUpperCase(name[end-1]):
  137. write(name[start:end])
  138. start = end
  139. case name[end] == '-',
  140. name[end] == '_',
  141. name[end] == ' ',
  142. name[end] == '\t',
  143. name[end] == '.':
  144. write(name[start:end])
  145. start = end + 1
  146. }
  147. end++
  148. }
  149. write(name[start:end])
  150. return b.String()
  151. }
  152. func normalizeCLIOptionName(name string) string {
  153. return normalizeOptionName(name, "-")
  154. }
  155. func normalizeEnvOptionName(name string) string {
  156. name = normalizeOptionName(name, "_")
  157. return strings.ToUpper(name)
  158. }
  159. func isUpperCase(b byte) bool {
  160. return b >= 'A' && b <= 'Z'
  161. }