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