github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/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 'v2ray 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  {{if eq (.UsageLine) (.Exec)}}
    61  Additional help topics:
    62  {{range .Commands}}{{if and (not .Runnable) (not .Commands)}}
    63  	{{.Name | width $.CommandsWidth}} {{.Short}}{{end}}{{end}}
    64  
    65  Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
    66  {{end}}
    67  `
    68  
    69  var helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}
    70  
    71  {{end}}{{.Long | trim}}
    72  `
    73  
    74  // An errWriter wraps a writer, recording whether a write error occurred.
    75  type errWriter struct {
    76  	w   io.Writer
    77  	err error
    78  }
    79  
    80  func (w *errWriter) Write(b []byte) (int, error) {
    81  	n, err := w.w.Write(b)
    82  	if err != nil {
    83  		w.err = err
    84  	}
    85  	return n, err
    86  }
    87  
    88  // tmpl executes the given template text on data, writing the result to w.
    89  func tmpl(w io.Writer, text string, data interface{}) {
    90  	t := template.New("top")
    91  	t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize, "width": width})
    92  	template.Must(t.Parse(text))
    93  	ew := &errWriter{w: w}
    94  	err := t.Execute(ew, data)
    95  	if ew.err != nil {
    96  		// I/O error writing. Ignore write on closed pipe.
    97  		if strings.Contains(ew.err.Error(), "pipe") {
    98  			SetExitStatus(1)
    99  			Exit()
   100  		}
   101  		Fatalf("writing output: %v", ew.err)
   102  	}
   103  	if err != nil {
   104  		panic(err)
   105  	}
   106  }
   107  
   108  func capitalize(s string) string {
   109  	if s == "" {
   110  		return s
   111  	}
   112  	r, n := utf8.DecodeRuneInString(s)
   113  	return string(unicode.ToTitle(r)) + s[n:]
   114  }
   115  
   116  func width(width int, value string) string {
   117  	format := fmt.Sprintf("%%-%ds", width)
   118  	return fmt.Sprintf(format, value)
   119  }
   120  
   121  // PrintUsage prints usage of cmd to w
   122  func PrintUsage(w io.Writer, cmd *Command) {
   123  	buildCommandText(cmd)
   124  	bw := bufio.NewWriter(w)
   125  	tmpl(bw, usageTemplate, makeTmplData(cmd))
   126  	bw.Flush()
   127  }
   128  
   129  // buildCommandText build command text as template
   130  func buildCommandText(cmd *Command) {
   131  	data := makeTmplData(cmd)
   132  	cmd.UsageLine = buildText(cmd.UsageLine, data)
   133  	// DO NOT SUPPORT ".Short":
   134  	// - It's not necessary
   135  	// - Or, we have to build text for all sub commands of current command, like "v2ray help api"
   136  	// cmd.Short = buildText(cmd.Short, data)
   137  	cmd.Long = buildText(cmd.Long, data)
   138  }
   139  
   140  func buildText(text string, data interface{}) string {
   141  	buf := bytes.NewBuffer([]byte{})
   142  	text = strings.ReplaceAll(text, "\t", "    ")
   143  	tmpl(buf, text, data)
   144  	return buf.String()
   145  }
   146  
   147  type tmplData struct {
   148  	*Command
   149  	*CommandEnvHolder
   150  }
   151  
   152  func makeTmplData(cmd *Command) tmplData {
   153  	// Minimum width of the command column
   154  	width := 12
   155  	for _, c := range cmd.Commands {
   156  		l := len(c.Name())
   157  		if width < l {
   158  			width = l
   159  		}
   160  	}
   161  	CommandEnv.CommandsWidth = width
   162  	return tmplData{
   163  		Command:          cmd,
   164  		CommandEnvHolder: &CommandEnv,
   165  	}
   166  }