1package kingpin
2
3import (
4	"bytes"
5	"fmt"
6	"go/doc"
7	"io"
8	"strings"
9
10	"github.com/alecthomas/template"
11)
12
13var (
14	preIndent = "  "
15)
16
17func formatTwoColumns(w io.Writer, indent, padding, width int, rows [][2]string) {
18	// Find size of first column.
19	s := 0
20	for _, row := range rows {
21		if c := len(row[0]); c > s && c < 30 {
22			s = c
23		}
24	}
25
26	indentStr := strings.Repeat(" ", indent)
27	offsetStr := strings.Repeat(" ", s+padding)
28
29	for _, row := range rows {
30		buf := bytes.NewBuffer(nil)
31		doc.ToText(buf, row[1], "", preIndent, width-s-padding-indent)
32		lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
33		fmt.Fprintf(w, "%s%-*s%*s", indentStr, s, row[0], padding, "")
34		if len(row[0]) >= 30 {
35			fmt.Fprintf(w, "\n%s%s", indentStr, offsetStr)
36		}
37		fmt.Fprintf(w, "%s\n", lines[0])
38		for _, line := range lines[1:] {
39			fmt.Fprintf(w, "%s%s%s\n", indentStr, offsetStr, line)
40		}
41	}
42}
43
44// Usage writes application usage to w. It parses args to determine
45// appropriate help context, such as which command to show help for.
46func (a *Application) Usage(args []string) {
47	context, err := a.parseContext(true, args)
48	a.FatalIfError(err, "")
49	if err := a.UsageForContextWithTemplate(context, 2, a.usageTemplate); err != nil {
50		panic(err)
51	}
52}
53
54func formatAppUsage(app *ApplicationModel) string {
55	s := []string{app.Name}
56	if len(app.Flags) > 0 {
57		s = append(s, app.FlagSummary())
58	}
59	if len(app.Args) > 0 {
60		s = append(s, app.ArgSummary())
61	}
62	return strings.Join(s, " ")
63}
64
65func formatCmdUsage(app *ApplicationModel, cmd *CmdModel) string {
66	s := []string{app.Name, cmd.String()}
67	if len(app.Flags) > 0 {
68		s = append(s, app.FlagSummary())
69	}
70	if len(app.Args) > 0 {
71		s = append(s, app.ArgSummary())
72	}
73	return strings.Join(s, " ")
74}
75
76func formatFlag(haveShort bool, flag *FlagModel) string {
77	flagString := ""
78	if flag.Short != 0 {
79		flagString += fmt.Sprintf("-%c, --%s", flag.Short, flag.Name)
80	} else {
81		if haveShort {
82			flagString += fmt.Sprintf("    --%s", flag.Name)
83		} else {
84			flagString += fmt.Sprintf("--%s", flag.Name)
85		}
86	}
87	if !flag.IsBoolFlag() {
88		flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
89	}
90	if v, ok := flag.Value.(repeatableFlag); ok && v.IsCumulative() {
91		flagString += " ..."
92	}
93	return flagString
94}
95
96type templateParseContext struct {
97	SelectedCommand *CmdModel
98	*FlagGroupModel
99	*ArgGroupModel
100}
101
102type templateContext struct {
103	App     *ApplicationModel
104	Width   int
105	Context *templateParseContext
106}
107
108// UsageForContext displays usage information from a ParseContext (obtained from
109// Application.ParseContext() or Action(f) callbacks).
110func (a *Application) UsageForContext(context *ParseContext) error {
111	return a.UsageForContextWithTemplate(context, 2, a.usageTemplate)
112}
113
114// UsageForContextWithTemplate is the base usage function. You generally don't need to use this.
115func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent int, tmpl string) error {
116	width := guessWidth(a.usageWriter)
117	funcs := template.FuncMap{
118		"Indent": func(level int) string {
119			return strings.Repeat(" ", level*indent)
120		},
121		"Wrap": func(indent int, s string) string {
122			buf := bytes.NewBuffer(nil)
123			indentText := strings.Repeat(" ", indent)
124			doc.ToText(buf, s, indentText, "  "+indentText, width-indent)
125			return buf.String()
126		},
127		"FormatFlag": formatFlag,
128		"FlagsToTwoColumns": func(f []*FlagModel) [][2]string {
129			rows := [][2]string{}
130			haveShort := false
131			for _, flag := range f {
132				if flag.Short != 0 {
133					haveShort = true
134					break
135				}
136			}
137			for _, flag := range f {
138				if !flag.Hidden {
139					rows = append(rows, [2]string{formatFlag(haveShort, flag), flag.Help})
140				}
141			}
142			return rows
143		},
144		"RequiredFlags": func(f []*FlagModel) []*FlagModel {
145			requiredFlags := []*FlagModel{}
146			for _, flag := range f {
147				if flag.Required {
148					requiredFlags = append(requiredFlags, flag)
149				}
150			}
151			return requiredFlags
152		},
153		"OptionalFlags": func(f []*FlagModel) []*FlagModel {
154			optionalFlags := []*FlagModel{}
155			for _, flag := range f {
156				if !flag.Required {
157					optionalFlags = append(optionalFlags, flag)
158				}
159			}
160			return optionalFlags
161		},
162		"ArgsToTwoColumns": func(a []*ArgModel) [][2]string {
163			rows := [][2]string{}
164			for _, arg := range a {
165				s := "<" + arg.Name + ">"
166				if !arg.Required {
167					s = "[" + s + "]"
168				}
169				rows = append(rows, [2]string{s, arg.Help})
170			}
171			return rows
172		},
173		"FormatTwoColumns": func(rows [][2]string) string {
174			buf := bytes.NewBuffer(nil)
175			formatTwoColumns(buf, indent, indent, width, rows)
176			return buf.String()
177		},
178		"FormatTwoColumnsWithIndent": func(rows [][2]string, indent, padding int) string {
179			buf := bytes.NewBuffer(nil)
180			formatTwoColumns(buf, indent, padding, width, rows)
181			return buf.String()
182		},
183		"FormatAppUsage":     formatAppUsage,
184		"FormatCommandUsage": formatCmdUsage,
185		"IsCumulative": func(value Value) bool {
186			r, ok := value.(remainderArg)
187			return ok && r.IsCumulative()
188		},
189		"Char": func(c rune) string {
190			return string(c)
191		},
192	}
193	t, err := template.New("usage").Funcs(funcs).Parse(tmpl)
194	if err != nil {
195		return err
196	}
197	var selectedCommand *CmdModel
198	if context.SelectedCommand != nil {
199		selectedCommand = context.SelectedCommand.Model()
200	}
201	ctx := templateContext{
202		App:   a.Model(),
203		Width: width,
204		Context: &templateParseContext{
205			SelectedCommand: selectedCommand,
206			FlagGroupModel:  context.flags.Model(),
207			ArgGroupModel:   context.arguments.Model(),
208		},
209	}
210	return t.Execute(a.usageWriter, ctx)
211}
212