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