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