help_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. package flags
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "os"
  7. "runtime"
  8. "strings"
  9. "testing"
  10. "time"
  11. )
  12. type helpOptions struct {
  13. Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information" ini-name:"verbose"`
  14. Call func(string) `short:"c" description:"Call phone number" ini-name:"call"`
  15. PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
  16. EmptyDescription bool `long:"empty-description"`
  17. Default string `long:"default" default:"Some\nvalue" description:"Test default value"`
  18. DefaultArray []string `long:"default-array" default:"Some value" default:"Other\tvalue" description:"Test default array value"`
  19. DefaultMap map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"`
  20. EnvDefault1 string `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"`
  21. EnvDefault2 string `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"`
  22. OptionWithArgName string `long:"opt-with-arg-name" value-name:"something" description:"Option with named argument"`
  23. OptionWithChoices string `long:"opt-with-choices" value-name:"choice" choice:"dog" choice:"cat" description:"Option with choices"`
  24. Hidden string `long:"hidden" description:"Hidden option" hidden:"yes"`
  25. OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
  26. Other struct {
  27. StringSlice []string `short:"s" default:"some" default:"value" description:"A slice of strings"`
  28. IntMap map[string]int `long:"intmap" default:"a:1" description:"A map from string to int" ini-name:"int-map"`
  29. } `group:"Other Options"`
  30. HiddenGroup struct {
  31. InsideHiddenGroup string `long:"inside-hidden-group" description:"Inside hidden group"`
  32. } `group:"Hidden group" hidden:"yes"`
  33. Group struct {
  34. Opt string `long:"opt" description:"This is a subgroup option"`
  35. HiddenInsideGroup string `long:"hidden-inside-group" description:"Hidden inside group" hidden:"yes"`
  36. NotHiddenInsideGroup string `long:"not-hidden-inside-group" description:"Not hidden inside group" hidden:"false"`
  37. Group struct {
  38. Opt string `long:"opt" description:"This is a subsubgroup option"`
  39. } `group:"Subsubgroup" namespace:"sap"`
  40. } `group:"Subgroup" namespace:"sip"`
  41. Command struct {
  42. ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
  43. } `command:"command" alias:"cm" alias:"cmd" description:"A command"`
  44. HiddenCommand struct {
  45. ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
  46. } `command:"hidden-command" description:"A hidden command" hidden:"yes"`
  47. Args struct {
  48. Filename string `positional-arg-name:"filename" description:"A filename with a long description to trigger line wrapping"`
  49. Number int `positional-arg-name:"num" description:"A number"`
  50. HiddenInHelp float32 `positional-arg-name:"hidden-in-help" required:"yes"`
  51. } `positional-args:"yes"`
  52. }
  53. func TestHelp(t *testing.T) {
  54. oldEnv := EnvSnapshot()
  55. defer oldEnv.Restore()
  56. os.Setenv("ENV_DEFAULT", "env-def")
  57. var opts helpOptions
  58. p := NewNamedParser("TestHelp", HelpFlag)
  59. p.AddGroup("Application Options", "The application options", &opts)
  60. _, err := p.ParseArgs([]string{"--help"})
  61. if err == nil {
  62. t.Fatalf("Expected help error")
  63. }
  64. if e, ok := err.(*Error); !ok {
  65. t.Fatalf("Expected flags.Error, but got %T", err)
  66. } else {
  67. if e.Type != ErrHelp {
  68. t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
  69. }
  70. var expected string
  71. if runtime.GOOS == "windows" {
  72. expected = `Usage:
  73. TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
  74. Application Options:
  75. /v, /verbose Show verbose debug information
  76. /c: Call phone number
  77. /ptrslice: A slice of pointers to string
  78. /empty-description
  79. /default: Test default value (default:
  80. "Some\nvalue")
  81. /default-array: Test default array value (default:
  82. Some value, "Other\tvalue")
  83. /default-map: Testdefault map value (default:
  84. some:value, another:value)
  85. /env-default1: Test env-default1 value (default:
  86. Some value) [%ENV_DEFAULT%]
  87. /env-default2: Test env-default2 value
  88. [%ENV_DEFAULT%]
  89. /opt-with-arg-name:something Option with named argument
  90. /opt-with-choices:choice[dog|cat] Option with choices
  91. Other Options:
  92. /s: A slice of strings (default: some,
  93. value)
  94. /intmap: A map from string to int (default:
  95. a:1)
  96. Subgroup:
  97. /sip.opt: This is a subgroup option
  98. /sip.not-hidden-inside-group: Not hidden inside group
  99. Subsubgroup:
  100. /sip.sap.opt: This is a subsubgroup option
  101. Help Options:
  102. /? Show this help message
  103. /h, /help Show this help message
  104. Arguments:
  105. filename: A filename with a long description
  106. to trigger line wrapping
  107. num: A number
  108. Available commands:
  109. command A command (aliases: cm, cmd)
  110. `
  111. } else {
  112. expected = `Usage:
  113. TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
  114. Application Options:
  115. -v, --verbose Show verbose debug information
  116. -c= Call phone number
  117. --ptrslice= A slice of pointers to string
  118. --empty-description
  119. --default= Test default value (default:
  120. "Some\nvalue")
  121. --default-array= Test default array value (default:
  122. Some value, "Other\tvalue")
  123. --default-map= Testdefault map value (default:
  124. some:value, another:value)
  125. --env-default1= Test env-default1 value (default:
  126. Some value) [$ENV_DEFAULT]
  127. --env-default2= Test env-default2 value
  128. [$ENV_DEFAULT]
  129. --opt-with-arg-name=something Option with named argument
  130. --opt-with-choices=choice[dog|cat] Option with choices
  131. Other Options:
  132. -s= A slice of strings (default: some,
  133. value)
  134. --intmap= A map from string to int (default:
  135. a:1)
  136. Subgroup:
  137. --sip.opt= This is a subgroup option
  138. --sip.not-hidden-inside-group= Not hidden inside group
  139. Subsubgroup:
  140. --sip.sap.opt= This is a subsubgroup option
  141. Help Options:
  142. -h, --help Show this help message
  143. Arguments:
  144. filename: A filename with a long description
  145. to trigger line wrapping
  146. num: A number
  147. Available commands:
  148. command A command (aliases: cm, cmd)
  149. `
  150. }
  151. assertDiff(t, e.Message, expected, "help message")
  152. }
  153. }
  154. func TestMan(t *testing.T) {
  155. oldEnv := EnvSnapshot()
  156. defer oldEnv.Restore()
  157. os.Setenv("ENV_DEFAULT", "env-def")
  158. var opts helpOptions
  159. p := NewNamedParser("TestMan", HelpFlag)
  160. p.ShortDescription = "Test manpage generation"
  161. p.LongDescription = "This is a somewhat `longer' description of what this does"
  162. p.AddGroup("Application Options", "The application options", &opts)
  163. p.Commands()[0].LongDescription = "Longer `command' description"
  164. var buf bytes.Buffer
  165. p.WriteManPage(&buf)
  166. got := buf.String()
  167. tt := time.Now()
  168. var envDefaultName string
  169. if runtime.GOOS == "windows" {
  170. envDefaultName = "%ENV_DEFAULT%"
  171. } else {
  172. envDefaultName = "$ENV_DEFAULT"
  173. }
  174. expected := fmt.Sprintf(`.TH TestMan 1 "%s"
  175. .SH NAME
  176. TestMan \- Test manpage generation
  177. .SH SYNOPSIS
  178. \fBTestMan\fP [OPTIONS]
  179. .SH DESCRIPTION
  180. This is a somewhat \fBlonger\fP description of what this does
  181. .SH OPTIONS
  182. .SS Application Options
  183. The application options
  184. .TP
  185. \fB\fB\-v\fR, \fB\-\-verbose\fR\fP
  186. Show verbose debug information
  187. .TP
  188. \fB\fB\-c\fR\fP
  189. Call phone number
  190. .TP
  191. \fB\fB\-\-ptrslice\fR\fP
  192. A slice of pointers to string
  193. .TP
  194. \fB\fB\-\-empty-description\fR\fP
  195. .TP
  196. \fB\fB\-\-default\fR <default: \fI"Some\\nvalue"\fR>\fP
  197. Test default value
  198. .TP
  199. \fB\fB\-\-default-array\fR <default: \fI"Some value", "Other\\tvalue"\fR>\fP
  200. Test default array value
  201. .TP
  202. \fB\fB\-\-default-map\fR <default: \fI"some:value", "another:value"\fR>\fP
  203. Testdefault map value
  204. .TP
  205. \fB\fB\-\-env-default1\fR <default: \fI"Some value"\fR>\fP
  206. Test env-default1 value
  207. .TP
  208. \fB\fB\-\-env-default2\fR <default: \fI%s\fR>\fP
  209. Test env-default2 value
  210. .TP
  211. \fB\fB\-\-opt-with-arg-name\fR \fIsomething\fR\fP
  212. Option with named argument
  213. .TP
  214. \fB\fB\-\-opt-with-choices\fR \fIchoice\fR\fP
  215. Option with choices
  216. .SS Other Options
  217. .TP
  218. \fB\fB\-s\fR <default: \fI"some", "value"\fR>\fP
  219. A slice of strings
  220. .TP
  221. \fB\fB\-\-intmap\fR <default: \fI"a:1"\fR>\fP
  222. A map from string to int
  223. .SS Subgroup
  224. .TP
  225. \fB\fB\-\-sip.opt\fR\fP
  226. This is a subgroup option
  227. .TP
  228. \fB\fB\-\-sip.not-hidden-inside-group\fR\fP
  229. Not hidden inside group
  230. .SS Subsubgroup
  231. .TP
  232. \fB\fB\-\-sip.sap.opt\fR\fP
  233. This is a subsubgroup option
  234. .SH COMMANDS
  235. .SS command
  236. A command
  237. Longer \fBcommand\fP description
  238. \fBUsage\fP: TestMan [OPTIONS] command [command-OPTIONS]
  239. .TP
  240. \fBAliases\fP: cm, cmd
  241. .TP
  242. \fB\fB\-\-extra-verbose\fR\fP
  243. Use for extra verbosity
  244. `, tt.Format("2 January 2006"), envDefaultName)
  245. assertDiff(t, got, expected, "man page")
  246. }
  247. type helpCommandNoOptions struct {
  248. Command struct {
  249. } `command:"command" description:"A command"`
  250. }
  251. func TestHelpCommand(t *testing.T) {
  252. oldEnv := EnvSnapshot()
  253. defer oldEnv.Restore()
  254. os.Setenv("ENV_DEFAULT", "env-def")
  255. var opts helpCommandNoOptions
  256. p := NewNamedParser("TestHelpCommand", HelpFlag)
  257. p.AddGroup("Application Options", "The application options", &opts)
  258. _, err := p.ParseArgs([]string{"command", "--help"})
  259. if err == nil {
  260. t.Fatalf("Expected help error")
  261. }
  262. if e, ok := err.(*Error); !ok {
  263. t.Fatalf("Expected flags.Error, but got %T", err)
  264. } else {
  265. if e.Type != ErrHelp {
  266. t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
  267. }
  268. var expected string
  269. if runtime.GOOS == "windows" {
  270. expected = `Usage:
  271. TestHelpCommand [OPTIONS] command
  272. Help Options:
  273. /? Show this help message
  274. /h, /help Show this help message
  275. `
  276. } else {
  277. expected = `Usage:
  278. TestHelpCommand [OPTIONS] command
  279. Help Options:
  280. -h, --help Show this help message
  281. `
  282. }
  283. assertDiff(t, e.Message, expected, "help message")
  284. }
  285. }
  286. func TestHelpDefaults(t *testing.T) {
  287. var expected string
  288. if runtime.GOOS == "windows" {
  289. expected = `Usage:
  290. TestHelpDefaults [OPTIONS]
  291. Application Options:
  292. /with-default: With default (default: default-value)
  293. /without-default: Without default
  294. /with-programmatic-default: With programmatic default (default:
  295. default-value)
  296. Help Options:
  297. /? Show this help message
  298. /h, /help Show this help message
  299. `
  300. } else {
  301. expected = `Usage:
  302. TestHelpDefaults [OPTIONS]
  303. Application Options:
  304. --with-default= With default (default: default-value)
  305. --without-default= Without default
  306. --with-programmatic-default= With programmatic default (default:
  307. default-value)
  308. Help Options:
  309. -h, --help Show this help message
  310. `
  311. }
  312. tests := []struct {
  313. Args []string
  314. Output string
  315. }{
  316. {
  317. Args: []string{"-h"},
  318. Output: expected,
  319. },
  320. {
  321. Args: []string{"--with-default", "other-value", "--with-programmatic-default", "other-value", "-h"},
  322. Output: expected,
  323. },
  324. }
  325. for _, test := range tests {
  326. var opts struct {
  327. WithDefault string `long:"with-default" default:"default-value" description:"With default"`
  328. WithoutDefault string `long:"without-default" description:"Without default"`
  329. WithProgrammaticDefault string `long:"with-programmatic-default" description:"With programmatic default"`
  330. }
  331. opts.WithProgrammaticDefault = "default-value"
  332. p := NewNamedParser("TestHelpDefaults", HelpFlag)
  333. p.AddGroup("Application Options", "The application options", &opts)
  334. _, err := p.ParseArgs(test.Args)
  335. if err == nil {
  336. t.Fatalf("Expected help error")
  337. }
  338. if e, ok := err.(*Error); !ok {
  339. t.Fatalf("Expected flags.Error, but got %T", err)
  340. } else {
  341. if e.Type != ErrHelp {
  342. t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
  343. }
  344. assertDiff(t, e.Message, test.Output, "help message")
  345. }
  346. }
  347. }
  348. func TestHelpRestArgs(t *testing.T) {
  349. opts := struct {
  350. Verbose bool `short:"v"`
  351. }{}
  352. p := NewNamedParser("TestHelpDefaults", HelpFlag)
  353. p.AddGroup("Application Options", "The application options", &opts)
  354. retargs, err := p.ParseArgs([]string{"-h", "-v", "rest"})
  355. if err == nil {
  356. t.Fatalf("Expected help error")
  357. }
  358. assertStringArray(t, retargs, []string{"-v", "rest"})
  359. }
  360. func TestWrapText(t *testing.T) {
  361. s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
  362. got := wrapText(s, 60, " ")
  363. expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
  364. sed do eiusmod tempor incididunt ut labore et dolore magna
  365. aliqua. Ut enim ad minim veniam, quis nostrud exercitation
  366. ullamco laboris nisi ut aliquip ex ea commodo consequat.
  367. Duis aute irure dolor in reprehenderit in voluptate velit
  368. esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
  369. occaecat cupidatat non proident, sunt in culpa qui officia
  370. deserunt mollit anim id est laborum.`
  371. assertDiff(t, got, expected, "wrapped text")
  372. }
  373. func TestWrapParagraph(t *testing.T) {
  374. s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n"
  375. s += "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\n"
  376. s += "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\n"
  377. s += "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"
  378. got := wrapText(s, 60, " ")
  379. expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
  380. sed do eiusmod tempor incididunt ut labore et dolore magna
  381. aliqua.
  382. Ut enim ad minim veniam, quis nostrud exercitation ullamco
  383. laboris nisi ut aliquip ex ea commodo consequat.
  384. Duis aute irure dolor in reprehenderit in voluptate velit
  385. esse cillum dolore eu fugiat nulla pariatur.
  386. Excepteur sint occaecat cupidatat non proident, sunt in
  387. culpa qui officia deserunt mollit anim id est laborum.
  388. `
  389. assertDiff(t, got, expected, "wrapped paragraph")
  390. }
  391. func TestHelpDefaultMask(t *testing.T) {
  392. var tests = []struct {
  393. opts interface{}
  394. present string
  395. }{
  396. {
  397. opts: &struct {
  398. Value string `short:"v" default:"123" description:"V"`
  399. }{},
  400. present: "V (default: 123)\n",
  401. },
  402. {
  403. opts: &struct {
  404. Value string `short:"v" default:"123" default-mask:"abc" description:"V"`
  405. }{},
  406. present: "V (default: abc)\n",
  407. },
  408. {
  409. opts: &struct {
  410. Value string `short:"v" default:"123" default-mask:"-" description:"V"`
  411. }{},
  412. present: "V\n",
  413. },
  414. {
  415. opts: &struct {
  416. Value string `short:"v" description:"V"`
  417. }{Value: "123"},
  418. present: "V (default: 123)\n",
  419. },
  420. {
  421. opts: &struct {
  422. Value string `short:"v" default-mask:"abc" description:"V"`
  423. }{Value: "123"},
  424. present: "V (default: abc)\n",
  425. },
  426. {
  427. opts: &struct {
  428. Value string `short:"v" default-mask:"-" description:"V"`
  429. }{Value: "123"},
  430. present: "V\n",
  431. },
  432. }
  433. for _, test := range tests {
  434. p := NewParser(test.opts, HelpFlag)
  435. _, err := p.ParseArgs([]string{"-h"})
  436. if flagsErr, ok := err.(*Error); ok && flagsErr.Type == ErrHelp {
  437. err = nil
  438. }
  439. if err != nil {
  440. t.Fatalf("Unexpected error: %v", err)
  441. }
  442. h := &bytes.Buffer{}
  443. w := bufio.NewWriter(h)
  444. p.writeHelpOption(w, p.FindOptionByShortName('v'), p.getAlignmentInfo())
  445. w.Flush()
  446. if strings.Index(h.String(), test.present) < 0 {
  447. t.Errorf("Not present %q\n%s", test.present, h.String())
  448. }
  449. }
  450. }