help.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Copyright 2012 Jesse van den Kieboom. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package flags
  5. import (
  6. "bufio"
  7. "bytes"
  8. "fmt"
  9. "io"
  10. "reflect"
  11. "strings"
  12. "unicode/utf8"
  13. )
  14. type alignmentInfo struct {
  15. maxLongLen int
  16. hasShort bool
  17. hasValueName bool
  18. terminalColumns int
  19. }
  20. func (p *Parser) getAlignmentInfo() alignmentInfo {
  21. ret := alignmentInfo{
  22. maxLongLen: 0,
  23. hasShort: false,
  24. hasValueName: false,
  25. terminalColumns: getTerminalColumns(),
  26. }
  27. if ret.terminalColumns <= 0 {
  28. ret.terminalColumns = 80
  29. }
  30. p.eachActiveGroup(func(grp *Group) {
  31. for _, info := range grp.options {
  32. if info.ShortName != 0 {
  33. ret.hasShort = true
  34. }
  35. lv := utf8.RuneCountInString(info.ValueName)
  36. if lv != 0 {
  37. ret.hasValueName = true
  38. }
  39. l := utf8.RuneCountInString(info.LongName) + lv
  40. if l > ret.maxLongLen {
  41. ret.maxLongLen = l
  42. }
  43. }
  44. })
  45. return ret
  46. }
  47. func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
  48. line := &bytes.Buffer{}
  49. distanceBetweenOptionAndDescription := 2
  50. paddingBeforeOption := 2
  51. line.WriteString(strings.Repeat(" ", paddingBeforeOption))
  52. if option.ShortName != 0 {
  53. line.WriteRune(defaultShortOptDelimiter)
  54. line.WriteRune(option.ShortName)
  55. } else if info.hasShort {
  56. line.WriteString(" ")
  57. }
  58. descstart := info.maxLongLen + paddingBeforeOption + distanceBetweenOptionAndDescription
  59. if info.hasShort {
  60. descstart += 2
  61. }
  62. if info.maxLongLen > 0 {
  63. descstart += 4
  64. }
  65. if info.hasValueName {
  66. descstart += 3
  67. }
  68. if len(option.LongName) > 0 {
  69. if option.ShortName != 0 {
  70. line.WriteString(", ")
  71. } else if info.hasShort {
  72. line.WriteString(" ")
  73. }
  74. line.WriteString(defaultLongOptDelimiter)
  75. line.WriteString(option.LongName)
  76. }
  77. if option.canArgument() {
  78. line.WriteRune(defaultNameArgDelimiter)
  79. if len(option.ValueName) > 0 {
  80. line.WriteString(option.ValueName)
  81. }
  82. }
  83. written := line.Len()
  84. line.WriteTo(writer)
  85. if option.Description != "" {
  86. dw := descstart - written
  87. writer.WriteString(strings.Repeat(" ", dw))
  88. def := ""
  89. defs := option.Default
  90. if len(option.DefaultMask) != 0 {
  91. if option.DefaultMask != "-" {
  92. def = option.DefaultMask
  93. }
  94. } else if len(defs) == 0 && option.canArgument() {
  95. var showdef bool
  96. switch option.field.Type.Kind() {
  97. case reflect.Func, reflect.Ptr:
  98. showdef = !option.value.IsNil()
  99. case reflect.Slice, reflect.String, reflect.Array:
  100. showdef = option.value.Len() > 0
  101. case reflect.Map:
  102. showdef = !option.value.IsNil() && option.value.Len() > 0
  103. default:
  104. zeroval := reflect.Zero(option.field.Type)
  105. showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
  106. }
  107. if showdef {
  108. def, _ = convertToString(option.value, option.tag)
  109. }
  110. } else if len(defs) != 0 {
  111. def = strings.Join(defs, ", ")
  112. }
  113. var desc string
  114. if def != "" {
  115. desc = fmt.Sprintf("%s (%v)", option.Description, def)
  116. } else {
  117. desc = option.Description
  118. }
  119. writer.WriteString(wrapText(desc,
  120. info.terminalColumns-descstart,
  121. strings.Repeat(" ", descstart)))
  122. }
  123. writer.WriteString("\n")
  124. }
  125. func maxCommandLength(s []*Command) int {
  126. if len(s) == 0 {
  127. return 0
  128. }
  129. ret := len(s[0].Name)
  130. for _, v := range s[1:] {
  131. l := len(v.Name)
  132. if l > ret {
  133. ret = l
  134. }
  135. }
  136. return ret
  137. }
  138. // WriteHelp writes a help message containing all the possible options and
  139. // their descriptions to the provided writer. Note that the HelpFlag parser
  140. // option provides a convenient way to add a -h/--help option group to the
  141. // command line parser which will automatically show the help messages using
  142. // this method.
  143. func (p *Parser) WriteHelp(writer io.Writer) {
  144. if writer == nil {
  145. return
  146. }
  147. wr := bufio.NewWriter(writer)
  148. aligninfo := p.getAlignmentInfo()
  149. cmd := p.Command
  150. for cmd.Active != nil {
  151. cmd = cmd.Active
  152. }
  153. if p.Name != "" {
  154. wr.WriteString("Usage:\n")
  155. wr.WriteString(" ")
  156. allcmd := p.Command
  157. for allcmd != nil {
  158. var usage string
  159. if allcmd == p.Command {
  160. if len(p.Usage) != 0 {
  161. usage = p.Usage
  162. } else {
  163. usage = "[OPTIONS]"
  164. }
  165. } else if us, ok := allcmd.data.(Usage); ok {
  166. usage = us.Usage()
  167. } else {
  168. usage = fmt.Sprintf("[%s-OPTIONS]", allcmd.Name)
  169. }
  170. if len(usage) != 0 {
  171. fmt.Fprintf(wr, " %s %s", allcmd.Name, usage)
  172. } else {
  173. fmt.Fprintf(wr, " %s", allcmd.Name)
  174. }
  175. allcmd = allcmd.Active
  176. }
  177. fmt.Fprintln(wr)
  178. if len(cmd.LongDescription) != 0 {
  179. fmt.Fprintln(wr)
  180. t := wrapText(cmd.LongDescription,
  181. aligninfo.terminalColumns,
  182. "")
  183. fmt.Fprintln(wr, t)
  184. }
  185. }
  186. p.eachActiveGroup(func(grp *Group) {
  187. first := true
  188. for _, info := range grp.options {
  189. if info.canCli() {
  190. if first {
  191. fmt.Fprintf(wr, "\n%s:\n", grp.ShortDescription)
  192. first = false
  193. }
  194. p.writeHelpOption(wr, info, aligninfo)
  195. }
  196. }
  197. })
  198. scommands := cmd.sortedCommands()
  199. if len(scommands) > 0 {
  200. maxnamelen := maxCommandLength(scommands)
  201. fmt.Fprintln(wr)
  202. fmt.Fprintln(wr, "Available commands:")
  203. for _, c := range scommands {
  204. fmt.Fprintf(wr, " %s", c.Name)
  205. if len(c.ShortDescription) > 0 {
  206. pad := strings.Repeat(" ", maxnamelen-len(c.Name))
  207. fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription)
  208. }
  209. fmt.Fprintln(wr)
  210. }
  211. }
  212. wr.Flush()
  213. }