github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/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  }