flags.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /*
  2. * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package z
  6. import (
  7. "errors"
  8. "fmt"
  9. "log"
  10. "os"
  11. "os/user"
  12. "path/filepath"
  13. "sort"
  14. "strconv"
  15. "strings"
  16. "time"
  17. )
  18. // SuperFlagHelp makes it really easy to generate command line `--help` output for a SuperFlag. For
  19. // example:
  20. //
  21. // const flagDefaults = `enabled=true; path=some/path;`
  22. //
  23. // var help string = z.NewSuperFlagHelp(flagDefaults).
  24. // Flag("enabled", "Turns on <something>.").
  25. // Flag("path", "The path to <something>.").
  26. // Flag("another", "Not present in defaults, but still included.").
  27. // String()
  28. //
  29. // The `help` string would then contain:
  30. //
  31. // enabled=true; Turns on <something>.
  32. // path=some/path; The path to <something>.
  33. // another=; Not present in defaults, but still included.
  34. //
  35. // All flags are sorted alphabetically for consistent `--help` output. Flags with default values are
  36. // placed at the top, and everything else goes under.
  37. type SuperFlagHelp struct {
  38. head string
  39. defaults *SuperFlag
  40. flags map[string]string
  41. }
  42. func NewSuperFlagHelp(defaults string) *SuperFlagHelp {
  43. return &SuperFlagHelp{
  44. defaults: NewSuperFlag(defaults),
  45. flags: make(map[string]string, 0),
  46. }
  47. }
  48. func (h *SuperFlagHelp) Head(head string) *SuperFlagHelp {
  49. h.head = head
  50. return h
  51. }
  52. func (h *SuperFlagHelp) Flag(name, description string) *SuperFlagHelp {
  53. h.flags[name] = description
  54. return h
  55. }
  56. func (h *SuperFlagHelp) String() string {
  57. defaultLines := make([]string, 0)
  58. otherLines := make([]string, 0)
  59. for name, help := range h.flags {
  60. val, found := h.defaults.m[name]
  61. line := fmt.Sprintf(" %s=%s; %s\n", name, val, help)
  62. if found {
  63. defaultLines = append(defaultLines, line)
  64. } else {
  65. otherLines = append(otherLines, line)
  66. }
  67. }
  68. sort.Strings(defaultLines)
  69. sort.Strings(otherLines)
  70. dls := strings.Join(defaultLines, "")
  71. ols := strings.Join(otherLines, "")
  72. if len(h.defaults.m) == 0 && len(ols) == 0 {
  73. // remove last newline
  74. dls = dls[:len(dls)-1]
  75. }
  76. // remove last newline
  77. if len(h.defaults.m) == 0 && len(ols) > 1 {
  78. ols = ols[:len(ols)-1]
  79. }
  80. return h.head + "\n" + dls + ols
  81. }
  82. func parseFlag(flag string) (map[string]string, error) {
  83. kvm := make(map[string]string)
  84. for _, kv := range strings.Split(flag, ";") {
  85. if strings.TrimSpace(kv) == "" {
  86. continue
  87. }
  88. // For a non-empty separator, 0 < len(splits) ≤ 2.
  89. splits := strings.SplitN(kv, "=", 2)
  90. k := strings.TrimSpace(splits[0])
  91. if len(splits) < 2 {
  92. return nil, fmt.Errorf("superflag: missing value for '%s' in flag: %s", k, flag)
  93. }
  94. k = strings.ToLower(k)
  95. k = strings.ReplaceAll(k, "_", "-")
  96. kvm[k] = strings.TrimSpace(splits[1])
  97. }
  98. return kvm, nil
  99. }
  100. type SuperFlag struct {
  101. m map[string]string
  102. }
  103. func NewSuperFlag(flag string) *SuperFlag {
  104. sf, err := newSuperFlagImpl(flag)
  105. if err != nil {
  106. log.Fatal(err)
  107. }
  108. return sf
  109. }
  110. func newSuperFlagImpl(flag string) (*SuperFlag, error) {
  111. m, err := parseFlag(flag)
  112. if err != nil {
  113. return nil, err
  114. }
  115. return &SuperFlag{m}, nil
  116. }
  117. func (sf *SuperFlag) String() string {
  118. if sf == nil {
  119. return ""
  120. }
  121. kvs := make([]string, 0, len(sf.m))
  122. for k, v := range sf.m {
  123. kvs = append(kvs, fmt.Sprintf("%s=%s", k, v))
  124. }
  125. return strings.Join(kvs, "; ")
  126. }
  127. func (sf *SuperFlag) MergeAndCheckDefault(flag string) *SuperFlag {
  128. sf, err := sf.mergeAndCheckDefaultImpl(flag)
  129. if err != nil {
  130. log.Fatal(err)
  131. }
  132. return sf
  133. }
  134. func (sf *SuperFlag) mergeAndCheckDefaultImpl(flag string) (*SuperFlag, error) {
  135. if sf == nil {
  136. m, err := parseFlag(flag)
  137. if err != nil {
  138. return nil, err
  139. }
  140. return &SuperFlag{m}, nil
  141. }
  142. src, err := parseFlag(flag)
  143. if err != nil {
  144. return nil, err
  145. }
  146. numKeys := len(sf.m)
  147. for k := range src {
  148. if _, ok := sf.m[k]; ok {
  149. numKeys--
  150. }
  151. }
  152. if numKeys != 0 {
  153. return nil, fmt.Errorf("superflag: found invalid options: %s.\nvalid options: %v", sf, flag)
  154. }
  155. for k, v := range src {
  156. if _, ok := sf.m[k]; !ok {
  157. sf.m[k] = v
  158. }
  159. }
  160. return sf, nil
  161. }
  162. func (sf *SuperFlag) Has(opt string) bool {
  163. val := sf.GetString(opt)
  164. return val != ""
  165. }
  166. func (sf *SuperFlag) GetDuration(opt string) time.Duration {
  167. val := sf.GetString(opt)
  168. if val == "" {
  169. return time.Duration(0)
  170. }
  171. if strings.Contains(val, "d") {
  172. val = strings.Replace(val, "d", "", 1)
  173. days, err := strconv.ParseInt(val, 0, 64)
  174. if err != nil {
  175. return time.Duration(0)
  176. }
  177. return time.Hour * 24 * time.Duration(days)
  178. }
  179. d, err := time.ParseDuration(val)
  180. if err != nil {
  181. return time.Duration(0)
  182. }
  183. return d
  184. }
  185. func (sf *SuperFlag) GetBool(opt string) bool {
  186. val := sf.GetString(opt)
  187. if val == "" {
  188. return false
  189. }
  190. b, err := strconv.ParseBool(val)
  191. if err != nil {
  192. err = errors.Join(err,
  193. fmt.Errorf("Unable to parse %s as bool for key: %s. Options: %s\n", val, opt, sf))
  194. log.Fatalf("%+v", err)
  195. }
  196. return b
  197. }
  198. func (sf *SuperFlag) GetFloat64(opt string) float64 {
  199. val := sf.GetString(opt)
  200. if val == "" {
  201. return 0
  202. }
  203. f, err := strconv.ParseFloat(val, 64)
  204. if err != nil {
  205. err = errors.Join(err,
  206. fmt.Errorf("Unable to parse %s as float64 for key: %s. Options: %s\n", val, opt, sf))
  207. log.Fatalf("%+v", err)
  208. }
  209. return f
  210. }
  211. func (sf *SuperFlag) GetInt64(opt string) int64 {
  212. val := sf.GetString(opt)
  213. if val == "" {
  214. return 0
  215. }
  216. i, err := strconv.ParseInt(val, 0, 64)
  217. if err != nil {
  218. err = errors.Join(err,
  219. fmt.Errorf("Unable to parse %s as int64 for key: %s. Options: %s\n", val, opt, sf))
  220. log.Fatalf("%+v", err)
  221. }
  222. return i
  223. }
  224. func (sf *SuperFlag) GetUint64(opt string) uint64 {
  225. val := sf.GetString(opt)
  226. if val == "" {
  227. return 0
  228. }
  229. u, err := strconv.ParseUint(val, 0, 64)
  230. if err != nil {
  231. err = errors.Join(err,
  232. fmt.Errorf("Unable to parse %s as uint64 for key: %s. Options: %s\n", val, opt, sf))
  233. log.Fatalf("%+v", err)
  234. }
  235. return u
  236. }
  237. func (sf *SuperFlag) GetUint32(opt string) uint32 {
  238. val := sf.GetString(opt)
  239. if val == "" {
  240. return 0
  241. }
  242. u, err := strconv.ParseUint(val, 0, 32)
  243. if err != nil {
  244. err = errors.Join(err,
  245. fmt.Errorf("Unable to parse %s as uint32 for key: %s. Options: %s\n", val, opt, sf))
  246. log.Fatalf("%+v", err)
  247. }
  248. return uint32(u)
  249. }
  250. func (sf *SuperFlag) GetString(opt string) string {
  251. if sf == nil {
  252. return ""
  253. }
  254. return sf.m[opt]
  255. }
  256. func (sf *SuperFlag) GetPath(opt string) string {
  257. p := sf.GetString(opt)
  258. path, err := expandPath(p)
  259. if err != nil {
  260. log.Fatalf("Failed to get path: %+v", err)
  261. }
  262. return path
  263. }
  264. // expandPath expands the paths containing ~ to /home/user. It also computes the absolute path
  265. // from the relative paths. For example: ~/abc/../cef will be transformed to /home/user/cef.
  266. func expandPath(path string) (string, error) {
  267. if len(path) == 0 {
  268. return "", nil
  269. }
  270. if path[0] == '~' && (len(path) == 1 || os.IsPathSeparator(path[1])) {
  271. usr, err := user.Current()
  272. if err != nil {
  273. return "", errors.Join(err, errors.New("Failed to get the home directory of the user"))
  274. }
  275. path = filepath.Join(usr.HomeDir, path[1:])
  276. }
  277. var err error
  278. path, err = filepath.Abs(path)
  279. if err != nil {
  280. return "", errors.Join(err, errors.New("Failed to generate absolute path"))
  281. }
  282. return path, nil
  283. }