flag.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. package cli
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "strings"
  8. "time"
  9. )
  10. // This flag enables bash-completion for all commands and subcommands
  11. var BashCompletionFlag = BoolFlag{
  12. Name: "generate-bash-completion",
  13. }
  14. // This flag prints the version for the application
  15. var VersionFlag = BoolFlag{
  16. Name: "version, v",
  17. Usage: "print the version",
  18. }
  19. // This flag prints the help for all commands and subcommands
  20. // Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
  21. // unless HideHelp is set to true)
  22. var HelpFlag = BoolFlag{
  23. Name: "help, h",
  24. Usage: "show help",
  25. }
  26. // Flag is a common interface related to parsing flags in cli.
  27. // For more advanced flag parsing techniques, it is recomended that
  28. // this interface be implemented.
  29. type Flag interface {
  30. fmt.Stringer
  31. // Apply Flag settings to the given flag set
  32. Apply(*flag.FlagSet)
  33. getName() string
  34. }
  35. func flagSet(name string, flags []Flag) *flag.FlagSet {
  36. set := flag.NewFlagSet(name, flag.ContinueOnError)
  37. for _, f := range flags {
  38. f.Apply(set)
  39. }
  40. return set
  41. }
  42. func eachName(longName string, fn func(string)) {
  43. parts := strings.Split(longName, ",")
  44. for _, name := range parts {
  45. name = strings.Trim(name, " ")
  46. fn(name)
  47. }
  48. }
  49. // Generic is a generic parseable type identified by a specific flag
  50. type Generic interface {
  51. Set(value string) error
  52. String() string
  53. }
  54. // GenericFlag is the flag type for types implementing Generic
  55. type GenericFlag struct {
  56. Name string
  57. Value Generic
  58. Usage string
  59. EnvVar string
  60. }
  61. // String returns the string representation of the generic flag to display the
  62. // help text to the user (uses the String() method of the generic flag to show
  63. // the value)
  64. func (f GenericFlag) String() string {
  65. return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
  66. }
  67. // Apply takes the flagset and calls Set on the generic flag with the value
  68. // provided by the user for parsing by the flag
  69. func (f GenericFlag) Apply(set *flag.FlagSet) {
  70. val := f.Value
  71. if f.EnvVar != "" {
  72. for _, envVar := range strings.Split(f.EnvVar, ",") {
  73. envVar = strings.TrimSpace(envVar)
  74. if envVal := os.Getenv(envVar); envVal != "" {
  75. val.Set(envVal)
  76. break
  77. }
  78. }
  79. }
  80. eachName(f.Name, func(name string) {
  81. set.Var(f.Value, name, f.Usage)
  82. })
  83. }
  84. func (f GenericFlag) getName() string {
  85. return f.Name
  86. }
  87. // StringSlice is an opaque type for []string to satisfy flag.Value
  88. type StringSlice []string
  89. // Set appends the string value to the list of values
  90. func (f *StringSlice) Set(value string) error {
  91. *f = append(*f, value)
  92. return nil
  93. }
  94. // String returns a readable representation of this value (for usage defaults)
  95. func (f *StringSlice) String() string {
  96. return fmt.Sprintf("%s", *f)
  97. }
  98. // Value returns the slice of strings set by this flag
  99. func (f *StringSlice) Value() []string {
  100. return *f
  101. }
  102. // StringSlice is a string flag that can be specified multiple times on the
  103. // command-line
  104. type StringSliceFlag struct {
  105. Name string
  106. Value *StringSlice
  107. Usage string
  108. EnvVar string
  109. }
  110. // String returns the usage
  111. func (f StringSliceFlag) String() string {
  112. firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
  113. pref := prefixFor(firstName)
  114. return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
  115. }
  116. // Apply populates the flag given the flag set and environment
  117. func (f StringSliceFlag) Apply(set *flag.FlagSet) {
  118. if f.EnvVar != "" {
  119. for _, envVar := range strings.Split(f.EnvVar, ",") {
  120. envVar = strings.TrimSpace(envVar)
  121. if envVal := os.Getenv(envVar); envVal != "" {
  122. newVal := &StringSlice{}
  123. for _, s := range strings.Split(envVal, ",") {
  124. s = strings.TrimSpace(s)
  125. newVal.Set(s)
  126. }
  127. f.Value = newVal
  128. break
  129. }
  130. }
  131. }
  132. eachName(f.Name, func(name string) {
  133. if f.Value == nil {
  134. f.Value = &StringSlice{}
  135. }
  136. set.Var(f.Value, name, f.Usage)
  137. })
  138. }
  139. func (f StringSliceFlag) getName() string {
  140. return f.Name
  141. }
  142. // StringSlice is an opaque type for []int to satisfy flag.Value
  143. type IntSlice []int
  144. // Set parses the value into an integer and appends it to the list of values
  145. func (f *IntSlice) Set(value string) error {
  146. tmp, err := strconv.Atoi(value)
  147. if err != nil {
  148. return err
  149. } else {
  150. *f = append(*f, tmp)
  151. }
  152. return nil
  153. }
  154. // String returns a readable representation of this value (for usage defaults)
  155. func (f *IntSlice) String() string {
  156. return fmt.Sprintf("%d", *f)
  157. }
  158. // Value returns the slice of ints set by this flag
  159. func (f *IntSlice) Value() []int {
  160. return *f
  161. }
  162. // IntSliceFlag is an int flag that can be specified multiple times on the
  163. // command-line
  164. type IntSliceFlag struct {
  165. Name string
  166. Value *IntSlice
  167. Usage string
  168. EnvVar string
  169. }
  170. // String returns the usage
  171. func (f IntSliceFlag) String() string {
  172. firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
  173. pref := prefixFor(firstName)
  174. return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
  175. }
  176. // Apply populates the flag given the flag set and environment
  177. func (f IntSliceFlag) Apply(set *flag.FlagSet) {
  178. if f.EnvVar != "" {
  179. for _, envVar := range strings.Split(f.EnvVar, ",") {
  180. envVar = strings.TrimSpace(envVar)
  181. if envVal := os.Getenv(envVar); envVal != "" {
  182. newVal := &IntSlice{}
  183. for _, s := range strings.Split(envVal, ",") {
  184. s = strings.TrimSpace(s)
  185. err := newVal.Set(s)
  186. if err != nil {
  187. fmt.Fprintf(os.Stderr, err.Error())
  188. }
  189. }
  190. f.Value = newVal
  191. break
  192. }
  193. }
  194. }
  195. eachName(f.Name, func(name string) {
  196. if f.Value == nil {
  197. f.Value = &IntSlice{}
  198. }
  199. set.Var(f.Value, name, f.Usage)
  200. })
  201. }
  202. func (f IntSliceFlag) getName() string {
  203. return f.Name
  204. }
  205. // BoolFlag is a switch that defaults to false
  206. type BoolFlag struct {
  207. Name string
  208. Usage string
  209. EnvVar string
  210. Destination *bool
  211. }
  212. // String returns a readable representation of this value (for usage defaults)
  213. func (f BoolFlag) String() string {
  214. return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
  215. }
  216. // Apply populates the flag given the flag set and environment
  217. func (f BoolFlag) Apply(set *flag.FlagSet) {
  218. val := false
  219. if f.EnvVar != "" {
  220. for _, envVar := range strings.Split(f.EnvVar, ",") {
  221. envVar = strings.TrimSpace(envVar)
  222. if envVal := os.Getenv(envVar); envVal != "" {
  223. envValBool, err := strconv.ParseBool(envVal)
  224. if err == nil {
  225. val = envValBool
  226. }
  227. break
  228. }
  229. }
  230. }
  231. eachName(f.Name, func(name string) {
  232. if f.Destination != nil {
  233. set.BoolVar(f.Destination, name, val, f.Usage)
  234. return
  235. }
  236. set.Bool(name, val, f.Usage)
  237. })
  238. }
  239. func (f BoolFlag) getName() string {
  240. return f.Name
  241. }
  242. // BoolTFlag this represents a boolean flag that is true by default, but can
  243. // still be set to false by --some-flag=false
  244. type BoolTFlag struct {
  245. Name string
  246. Usage string
  247. EnvVar string
  248. Destination *bool
  249. }
  250. // String returns a readable representation of this value (for usage defaults)
  251. func (f BoolTFlag) String() string {
  252. return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
  253. }
  254. // Apply populates the flag given the flag set and environment
  255. func (f BoolTFlag) Apply(set *flag.FlagSet) {
  256. val := true
  257. if f.EnvVar != "" {
  258. for _, envVar := range strings.Split(f.EnvVar, ",") {
  259. envVar = strings.TrimSpace(envVar)
  260. if envVal := os.Getenv(envVar); envVal != "" {
  261. envValBool, err := strconv.ParseBool(envVal)
  262. if err == nil {
  263. val = envValBool
  264. break
  265. }
  266. }
  267. }
  268. }
  269. eachName(f.Name, func(name string) {
  270. if f.Destination != nil {
  271. set.BoolVar(f.Destination, name, val, f.Usage)
  272. return
  273. }
  274. set.Bool(name, val, f.Usage)
  275. })
  276. }
  277. func (f BoolTFlag) getName() string {
  278. return f.Name
  279. }
  280. // StringFlag represents a flag that takes as string value
  281. type StringFlag struct {
  282. Name string
  283. Value string
  284. Usage string
  285. EnvVar string
  286. Destination *string
  287. }
  288. // String returns the usage
  289. func (f StringFlag) String() string {
  290. var fmtString string
  291. fmtString = "%s %v\t%v"
  292. if len(f.Value) > 0 {
  293. fmtString = "%s \"%v\"\t%v"
  294. } else {
  295. fmtString = "%s %v\t%v"
  296. }
  297. return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
  298. }
  299. // Apply populates the flag given the flag set and environment
  300. func (f StringFlag) Apply(set *flag.FlagSet) {
  301. if f.EnvVar != "" {
  302. for _, envVar := range strings.Split(f.EnvVar, ",") {
  303. envVar = strings.TrimSpace(envVar)
  304. if envVal := os.Getenv(envVar); envVal != "" {
  305. f.Value = envVal
  306. break
  307. }
  308. }
  309. }
  310. eachName(f.Name, func(name string) {
  311. if f.Destination != nil {
  312. set.StringVar(f.Destination, name, f.Value, f.Usage)
  313. return
  314. }
  315. set.String(name, f.Value, f.Usage)
  316. })
  317. }
  318. func (f StringFlag) getName() string {
  319. return f.Name
  320. }
  321. // IntFlag is a flag that takes an integer
  322. // Errors if the value provided cannot be parsed
  323. type IntFlag struct {
  324. Name string
  325. Value int
  326. Usage string
  327. EnvVar string
  328. Destination *int
  329. }
  330. // String returns the usage
  331. func (f IntFlag) String() string {
  332. return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
  333. }
  334. // Apply populates the flag given the flag set and environment
  335. func (f IntFlag) Apply(set *flag.FlagSet) {
  336. if f.EnvVar != "" {
  337. for _, envVar := range strings.Split(f.EnvVar, ",") {
  338. envVar = strings.TrimSpace(envVar)
  339. if envVal := os.Getenv(envVar); envVal != "" {
  340. envValInt, err := strconv.ParseInt(envVal, 0, 64)
  341. if err == nil {
  342. f.Value = int(envValInt)
  343. break
  344. }
  345. }
  346. }
  347. }
  348. eachName(f.Name, func(name string) {
  349. if f.Destination != nil {
  350. set.IntVar(f.Destination, name, f.Value, f.Usage)
  351. return
  352. }
  353. set.Int(name, f.Value, f.Usage)
  354. })
  355. }
  356. func (f IntFlag) getName() string {
  357. return f.Name
  358. }
  359. // DurationFlag is a flag that takes a duration specified in Go's duration
  360. // format: https://golang.org/pkg/time/#ParseDuration
  361. type DurationFlag struct {
  362. Name string
  363. Value time.Duration
  364. Usage string
  365. EnvVar string
  366. Destination *time.Duration
  367. }
  368. // String returns a readable representation of this value (for usage defaults)
  369. func (f DurationFlag) String() string {
  370. return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
  371. }
  372. // Apply populates the flag given the flag set and environment
  373. func (f DurationFlag) Apply(set *flag.FlagSet) {
  374. if f.EnvVar != "" {
  375. for _, envVar := range strings.Split(f.EnvVar, ",") {
  376. envVar = strings.TrimSpace(envVar)
  377. if envVal := os.Getenv(envVar); envVal != "" {
  378. envValDuration, err := time.ParseDuration(envVal)
  379. if err == nil {
  380. f.Value = envValDuration
  381. break
  382. }
  383. }
  384. }
  385. }
  386. eachName(f.Name, func(name string) {
  387. if f.Destination != nil {
  388. set.DurationVar(f.Destination, name, f.Value, f.Usage)
  389. return
  390. }
  391. set.Duration(name, f.Value, f.Usage)
  392. })
  393. }
  394. func (f DurationFlag) getName() string {
  395. return f.Name
  396. }
  397. // Float64Flag is a flag that takes an float value
  398. // Errors if the value provided cannot be parsed
  399. type Float64Flag struct {
  400. Name string
  401. Value float64
  402. Usage string
  403. EnvVar string
  404. Destination *float64
  405. }
  406. // String returns the usage
  407. func (f Float64Flag) String() string {
  408. return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
  409. }
  410. // Apply populates the flag given the flag set and environment
  411. func (f Float64Flag) Apply(set *flag.FlagSet) {
  412. if f.EnvVar != "" {
  413. for _, envVar := range strings.Split(f.EnvVar, ",") {
  414. envVar = strings.TrimSpace(envVar)
  415. if envVal := os.Getenv(envVar); envVal != "" {
  416. envValFloat, err := strconv.ParseFloat(envVal, 10)
  417. if err == nil {
  418. f.Value = float64(envValFloat)
  419. }
  420. }
  421. }
  422. }
  423. eachName(f.Name, func(name string) {
  424. if f.Destination != nil {
  425. set.Float64Var(f.Destination, name, f.Value, f.Usage)
  426. return
  427. }
  428. set.Float64(name, f.Value, f.Usage)
  429. })
  430. }
  431. func (f Float64Flag) getName() string {
  432. return f.Name
  433. }
  434. func prefixFor(name string) (prefix string) {
  435. if len(name) == 1 {
  436. prefix = "-"
  437. } else {
  438. prefix = "--"
  439. }
  440. return
  441. }
  442. func prefixedNames(fullName string) (prefixed string) {
  443. parts := strings.Split(fullName, ",")
  444. for i, name := range parts {
  445. name = strings.Trim(name, " ")
  446. prefixed += prefixFor(name) + name
  447. if i < len(parts)-1 {
  448. prefixed += ", "
  449. }
  450. }
  451. return
  452. }
  453. func withEnvHint(envVar, str string) string {
  454. envText := ""
  455. if envVar != "" {
  456. envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
  457. }
  458. return str + envText
  459. }