usage.go 5.4 KB

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