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