1package arg
  2
  3import (
  4	"fmt"
  5	"io"
  6	"os"
  7	"strings"
  8)
  9
 10// the width of the left column
 11const colWidth = 25
 12
 13// to allow monkey patching in tests
 14var (
 15	stdout io.Writer = os.Stdout
 16	stderr io.Writer = os.Stderr
 17	osExit           = os.Exit
 18)
 19
 20// Fail prints usage information to stderr and exits with non-zero status
 21func (p *Parser) Fail(msg string) {
 22	p.failWithSubcommand(msg, p.cmd)
 23}
 24
 25// FailSubcommand prints usage information for a specified subcommand to stderr,
 26// then exits with non-zero status. To write usage information for a top-level
 27// subcommand, provide just the name of that subcommand. To write usage
 28// information for a subcommand that is nested under another subcommand, provide
 29// a sequence of subcommand names starting with the top-level subcommand and so
 30// on down the tree.
 31func (p *Parser) FailSubcommand(msg string, subcommand ...string) error {
 32	cmd, err := p.lookupCommand(subcommand...)
 33	if err != nil {
 34		return err
 35	}
 36	p.failWithSubcommand(msg, cmd)
 37	return nil
 38}
 39
 40// failWithSubcommand prints usage information for the given subcommand to stderr and exits with non-zero status
 41func (p *Parser) failWithSubcommand(msg string, cmd *command) {
 42	p.writeUsageForSubcommand(stderr, cmd)
 43	fmt.Fprintln(stderr, "error:", msg)
 44	osExit(-1)
 45}
 46
 47// WriteUsage writes usage information to the given writer
 48func (p *Parser) WriteUsage(w io.Writer) {
 49	cmd := p.cmd
 50	if p.lastCmd != nil {
 51		cmd = p.lastCmd
 52	}
 53	p.writeUsageForSubcommand(w, cmd)
 54}
 55
 56// WriteUsageForSubcommand writes the usage information for a specified
 57// subcommand. To write usage information for a top-level subcommand, provide
 58// just the name of that subcommand. To write usage information for a subcommand
 59// that is nested under another subcommand, provide a sequence of subcommand
 60// names starting with the top-level subcommand and so on down the tree.
 61func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) error {
 62	cmd, err := p.lookupCommand(subcommand...)
 63	if err != nil {
 64		return err
 65	}
 66	p.writeUsageForSubcommand(w, cmd)
 67	return nil
 68}
 69
 70// writeUsageForSubcommand writes usage information for the given subcommand
 71func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
 72	var positionals, longOptions, shortOptions []*spec
 73	for _, spec := range cmd.specs {
 74		switch {
 75		case spec.positional:
 76			positionals = append(positionals, spec)
 77		case spec.long != "":
 78			longOptions = append(longOptions, spec)
 79		case spec.short != "":
 80			shortOptions = append(shortOptions, spec)
 81		}
 82	}
 83
 84	if p.version != "" {
 85		fmt.Fprintln(w, p.version)
 86	}
 87
 88	// make a list of ancestor commands so that we print with full context
 89	var ancestors []string
 90	ancestor := cmd
 91	for ancestor != nil {
 92		ancestors = append(ancestors, ancestor.name)
 93		ancestor = ancestor.parent
 94	}
 95
 96	// print the beginning of the usage string
 97	fmt.Fprint(w, "Usage:")
 98	for i := len(ancestors) - 1; i >= 0; i-- {
 99		fmt.Fprint(w, " "+ancestors[i])
100	}
101
102	// write the option component of the usage message
103	for _, spec := range shortOptions {
104		// prefix with a space
105		fmt.Fprint(w, " ")
106		if !spec.required {
107			fmt.Fprint(w, "[")
108		}
109		fmt.Fprint(w, synopsis(spec, "-"+spec.short))
110		if !spec.required {
111			fmt.Fprint(w, "]")
112		}
113	}
114
115	for _, spec := range longOptions {
116		// prefix with a space
117		fmt.Fprint(w, " ")
118		if !spec.required {
119			fmt.Fprint(w, "[")
120		}
121		fmt.Fprint(w, synopsis(spec, "--"+spec.long))
122		if !spec.required {
123			fmt.Fprint(w, "]")
124		}
125	}
126
127	// When we parse positionals, we check that:
128	//  1. required positionals come before non-required positionals
129	//  2. there is at most one multiple-value positional
130	//  3. if there is a multiple-value positional then it comes after all other positionals
131	// Here we merely print the usage string, so we do not explicitly re-enforce those rules
132
133	// write the positionals in following form:
134	//    REQUIRED1 REQUIRED2
135	//    REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]]
136	//    REQUIRED1 REQUIRED2 REPEATED [REPEATED ...]
137	//    REQUIRED1 REQUIRED2 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]
138	//    REQUIRED1 REQUIRED2 [OPTIONAL1 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]]
139	var closeBrackets int
140	for _, spec := range positionals {
141		fmt.Fprint(w, " ")
142		if !spec.required {
143			fmt.Fprint(w, "[")
144			closeBrackets += 1
145		}
146		if spec.cardinality == multiple {
147			fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder)
148		} else {
149			fmt.Fprint(w, spec.placeholder)
150		}
151	}
152	fmt.Fprint(w, strings.Repeat("]", closeBrackets))
153
154	// if the program supports subcommands, give a hint to the user about their existence
155	if len(cmd.subcommands) > 0 {
156		fmt.Fprint(w, " <command> [<args>]")
157	}
158
159	fmt.Fprint(w, "\n")
160}
161
162func printTwoCols(w io.Writer, left, help string, defaultVal string, envVal string) {
163	lhs := "  " + left
164	fmt.Fprint(w, lhs)
165	if help != "" {
166		if len(lhs)+2 < colWidth {
167			fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs)))
168		} else {
169			fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth))
170		}
171		fmt.Fprint(w, help)
172	}
173
174	bracketsContent := []string{}
175
176	if defaultVal != "" {
177		bracketsContent = append(bracketsContent,
178			fmt.Sprintf("default: %s", defaultVal),
179		)
180	}
181
182	if envVal != "" {
183		bracketsContent = append(bracketsContent,
184			fmt.Sprintf("env: %s", envVal),
185		)
186	}
187
188	if len(bracketsContent) > 0 {
189		fmt.Fprintf(w, " [%s]", strings.Join(bracketsContent, ", "))
190	}
191	fmt.Fprint(w, "\n")
192}
193
194// WriteHelp writes the usage string followed by the full help string for each option
195func (p *Parser) WriteHelp(w io.Writer) {
196	cmd := p.cmd
197	if p.lastCmd != nil {
198		cmd = p.lastCmd
199	}
200	p.writeHelpForSubcommand(w, cmd)
201}
202
203// WriteHelpForSubcommand writes the usage string followed by the full help
204// string for a specified subcommand. To write help for a top-level subcommand,
205// provide just the name of that subcommand. To write help for a subcommand that
206// is nested under another subcommand, provide a sequence of subcommand names
207// starting with the top-level subcommand and so on down the tree.
208func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error {
209	cmd, err := p.lookupCommand(subcommand...)
210	if err != nil {
211		return err
212	}
213	p.writeHelpForSubcommand(w, cmd)
214	return nil
215}
216
217// writeHelp writes the usage string for the given subcommand
218func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
219	var positionals, longOptions, shortOptions []*spec
220	for _, spec := range cmd.specs {
221		switch {
222		case spec.positional:
223			positionals = append(positionals, spec)
224		case spec.long != "":
225			longOptions = append(longOptions, spec)
226		case spec.short != "":
227			shortOptions = append(shortOptions, spec)
228		}
229	}
230
231	if p.description != "" {
232		fmt.Fprintln(w, p.description)
233	}
234	p.writeUsageForSubcommand(w, cmd)
235
236	// write the list of positionals
237	if len(positionals) > 0 {
238		fmt.Fprint(w, "\nPositional arguments:\n")
239		for _, spec := range positionals {
240			printTwoCols(w, spec.placeholder, spec.help, "", "")
241		}
242	}
243
244	// write the list of options with the short-only ones first to match the usage string
245	if len(shortOptions)+len(longOptions) > 0 || cmd.parent == nil {
246		fmt.Fprint(w, "\nOptions:\n")
247		for _, spec := range shortOptions {
248			p.printOption(w, spec)
249		}
250		for _, spec := range longOptions {
251			p.printOption(w, spec)
252		}
253	}
254
255	// obtain a flattened list of options from all ancestors
256	var globals []*spec
257	ancestor := cmd.parent
258	for ancestor != nil {
259		globals = append(globals, ancestor.specs...)
260		ancestor = ancestor.parent
261	}
262
263	// write the list of global options
264	if len(globals) > 0 {
265		fmt.Fprint(w, "\nGlobal options:\n")
266		for _, spec := range globals {
267			p.printOption(w, spec)
268		}
269	}
270
271	// write the list of built in options
272	p.printOption(w, &spec{
273		cardinality: zero,
274		long:        "help",
275		short:       "h",
276		help:        "display this help and exit",
277	})
278	if p.version != "" {
279		p.printOption(w, &spec{
280			cardinality: zero,
281			long:        "version",
282			help:        "display version and exit",
283		})
284	}
285
286	// write the list of subcommands
287	if len(cmd.subcommands) > 0 {
288		fmt.Fprint(w, "\nCommands:\n")
289		for _, subcmd := range cmd.subcommands {
290			printTwoCols(w, subcmd.name, subcmd.help, "", "")
291		}
292	}
293}
294
295func (p *Parser) printOption(w io.Writer, spec *spec) {
296	ways := make([]string, 0, 2)
297	if spec.long != "" {
298		ways = append(ways, synopsis(spec, "--"+spec.long))
299	}
300	if spec.short != "" {
301		ways = append(ways, synopsis(spec, "-"+spec.short))
302	}
303	if len(ways) > 0 {
304		printTwoCols(w, strings.Join(ways, ", "), spec.help, spec.defaultVal, spec.env)
305	}
306}
307
308// lookupCommand finds a subcommand based on a sequence of subcommand names. The
309// first string should be a top-level subcommand, the next should be a child
310// subcommand of that subcommand, and so on. If no strings are given then the
311// root command is returned. If no such subcommand exists then an error is
312// returned.
313func (p *Parser) lookupCommand(path ...string) (*command, error) {
314	cmd := p.cmd
315	for _, name := range path {
316		var found *command
317		for _, child := range cmd.subcommands {
318			if child.name == name {
319				found = child
320			}
321		}
322		if found == nil {
323			return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name)
324		}
325		cmd = found
326	}
327	return cmd, nil
328}
329
330func synopsis(spec *spec, form string) string {
331	if spec.cardinality == zero {
332		return form
333	}
334	return form + " " + spec.placeholder
335}