summaryrefslogtreecommitdiff
path: root/usage.go
blob: 5155d820eece090e01914ad080127a1d8ee04b0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package arg

import (
	"fmt"
	"io"
	"os"
	"reflect"
	"strings"
)

// Usage prints usage information to stdout information and exits with status zero
func Usage(dest ...interface{}) {
	if err := WriteUsage(os.Stdout, dest...); err != nil {
		fmt.Println(err)
	}
	os.Exit(0)
}

// Fail prints usage information to stdout and exits with non-zero status
func Fail(msg string, dest ...interface{}) {
	fmt.Println(msg)
	if err := WriteUsage(os.Stdout, dest...); err != nil {
		fmt.Println(err)
	}
	os.Exit(1)
}

// WriteUsage writes usage information to the given writer
func WriteUsage(w io.Writer, dest ...interface{}) error {
	spec, err := extractSpec(dest...)
	if err != nil {
		return err
	}
	writeUsage(w, spec)
	return nil
}

func synopsis(spec *spec, form string) string {
	if spec.dest.Kind() == reflect.Bool {
		return form
	} else {
		return form + " " + strings.ToUpper(spec.long)
	}
}

// writeUsage writes usage information to the given writer
func writeUsage(w io.Writer, specs []*spec) {
	var positionals, options []*spec
	for _, spec := range specs {
		if spec.positional {
			positionals = append(positionals, spec)
		} else {
			options = append(options, spec)
		}
	}

	fmt.Fprint(w, "usage: ")

	// write the option component of the one-line usage message
	for _, spec := range options {
		if !spec.required {
			fmt.Fprint(w, "[")
		}
		fmt.Fprint(w, synopsis(spec, "--"+spec.long))
		if !spec.required {
			fmt.Fprint(w, "]")
		}
		fmt.Fprint(w, " ")
	}

	// write the positional component of the one-line usage message
	for _, spec := range positionals {
		up := strings.ToUpper(spec.long)
		if spec.multiple {
			fmt.Fprintf(w, "[%s [%s ...]]", up, up)
		} else {
			fmt.Fprint(w, up)
		}
		fmt.Fprint(w, " ")
	}
	fmt.Fprint(w, "\n")

	// write the list of positionals
	if len(positionals) > 0 {
		fmt.Fprint(w, "\npositional arguments:\n")
		for _, spec := range positionals {
			fmt.Fprintf(w, "  %s\n", spec.long)
		}
	}

	// write the list of options
	if len(options) > 0 {
		fmt.Fprint(w, "\noptions:\n")
		const colWidth = 25
		for _, spec := range options {
			left := fmt.Sprint(synopsis(spec, "--"+spec.long))
			if spec.short != "" {
				left += ", " + fmt.Sprint(synopsis(spec, "-"+spec.short))
			}
			fmt.Print(left)
			if spec.help != "" {
				if len(left)+2 < colWidth {
					fmt.Fprint(w, strings.Repeat(" ", colWidth-len(left)))
				} else {
					fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth))
				}
				fmt.Fprint(w, spec.help)
			}
			fmt.Fprint(w, "\n")
		}
	}
}