help.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright 2017 The Go Authors. 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 base
  5. import (
  6. "bufio"
  7. "bytes"
  8. "fmt"
  9. "io"
  10. "os"
  11. "strings"
  12. "text/template"
  13. "unicode"
  14. "unicode/utf8"
  15. )
  16. // Help implements the 'help' command.
  17. func Help(w io.Writer, args []string) {
  18. cmd := RootCommand
  19. Args:
  20. for i, arg := range args {
  21. for _, sub := range cmd.Commands {
  22. if sub.Name() == arg {
  23. cmd = sub
  24. continue Args
  25. }
  26. }
  27. // helpSuccess is the help command using as many args as possible that would succeed.
  28. helpSuccess := CommandEnv.Exec + " help"
  29. if i > 0 {
  30. helpSuccess += " " + strings.Join(args[:i], " ")
  31. }
  32. fmt.Fprintf(os.Stderr, "%s help %s: unknown help topic. Run '%s'.\n", CommandEnv.Exec, strings.Join(args, " "), helpSuccess)
  33. SetExitStatus(2) // failed at 'xray help cmd'
  34. Exit()
  35. }
  36. if len(cmd.Commands) > 0 {
  37. PrintUsage(os.Stdout, cmd)
  38. } else {
  39. buildCommandText(cmd)
  40. tmpl(os.Stdout, helpTemplate, makeTmplData(cmd))
  41. }
  42. }
  43. var usageTemplate = `{{.Long | trim}}
  44. Usage:
  45. {{.UsageLine}} <command> [arguments]
  46. The commands are:
  47. {{range .Commands}}{{if and (ne .Short "") (or (.Runnable) .Commands)}}
  48. {{.Name | width $.CommandsWidth}} {{.Short}}{{end}}{{end}}
  49. Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <command>" for more information about a command.
  50. `
  51. // APPEND FOLLOWING TO 'usageTemplate' IF YOU WANT DOC,
  52. // A DOC TOPIC IS JUST A COMMAND NOT RUNNABLE:
  53. //
  54. // {{if eq (.UsageLine) (.Exec)}}
  55. // Additional help topics:
  56. // {{range .Commands}}{{if and (not .Runnable) (not .Commands)}}
  57. // {{.Name | printf "%-15s"}} {{.Short}}{{end}}{{end}}
  58. //
  59. // Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
  60. // {{end}}
  61. var helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}
  62. {{end}}{{.Long | trim}}
  63. `
  64. // An errWriter wraps a writer, recording whether a write error occurred.
  65. type errWriter struct {
  66. w io.Writer
  67. err error
  68. }
  69. func (w *errWriter) Write(b []byte) (int, error) {
  70. n, err := w.w.Write(b)
  71. if err != nil {
  72. w.err = err
  73. }
  74. return n, err
  75. }
  76. // tmpl executes the given template text on data, writing the result to w.
  77. func tmpl(w io.Writer, text string, data interface{}) {
  78. t := template.New("top")
  79. t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize, "width": width})
  80. template.Must(t.Parse(text))
  81. ew := &errWriter{w: w}
  82. err := t.Execute(ew, data)
  83. if ew.err != nil {
  84. // I/O error writing. Ignore write on closed pipe.
  85. if strings.Contains(ew.err.Error(), "pipe") {
  86. SetExitStatus(1)
  87. Exit()
  88. }
  89. Fatalf("writing output: %v", ew.err)
  90. }
  91. if err != nil {
  92. panic(err)
  93. }
  94. }
  95. func capitalize(s string) string {
  96. if s == "" {
  97. return s
  98. }
  99. r, n := utf8.DecodeRuneInString(s)
  100. return string(unicode.ToTitle(r)) + s[n:]
  101. }
  102. func width(width int, value string) string {
  103. format := fmt.Sprintf("%%-%ds", width)
  104. return fmt.Sprintf(format, value)
  105. }
  106. // PrintUsage prints usage of cmd to w
  107. func PrintUsage(w io.Writer, cmd *Command) {
  108. buildCommandText(cmd)
  109. bw := bufio.NewWriter(w)
  110. tmpl(bw, usageTemplate, makeTmplData(cmd))
  111. bw.Flush()
  112. }
  113. // buildCommandText build command text as template
  114. func buildCommandText(cmd *Command) {
  115. data := makeTmplData(cmd)
  116. cmd.UsageLine = buildText(cmd.UsageLine, data)
  117. // DO NOT SUPPORT ".Short":
  118. // - It's not necessary
  119. // - Or, we have to build text for all sub commands of current command, like "xray help api"
  120. // cmd.Short = buildText(cmd.Short, data)
  121. cmd.Long = buildText(cmd.Long, data)
  122. }
  123. func buildText(text string, data interface{}) string {
  124. buf := bytes.NewBuffer([]byte{})
  125. tmpl(buf, text, data)
  126. return buf.String()
  127. }
  128. type tmplData struct {
  129. *Command
  130. *CommandEnvHolder
  131. }
  132. func makeTmplData(cmd *Command) tmplData {
  133. // Minimum width of the command column
  134. width := 12
  135. for _, c := range cmd.Commands {
  136. l := len(c.Name())
  137. if width < l {
  138. width = l
  139. }
  140. }
  141. CommandEnv.CommandsWidth = width
  142. return tmplData{
  143. Command: cmd,
  144. CommandEnvHolder: &CommandEnv,
  145. }
  146. }