Source file src/cmd/go/internal/help/help.go

     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 help implements the “go help” command.
     6  package help
     7  
     8  import (
     9  	"bufio"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"strings"
    14  	"text/template"
    15  	"unicode"
    16  	"unicode/utf8"
    17  
    18  	"cmd/go/internal/base"
    19  	"cmd/internal/telemetry/counter"
    20  )
    21  
    22  var counterErrorsHelpUnknownTopic = counter.New("go/errors:help-unknown-topic")
    23  
    24  // Help implements the 'help' command.
    25  func Help(w io.Writer, args []string) {
    26  	// 'go help documentation' generates doc.go.
    27  	if len(args) == 1 && args[0] == "documentation" {
    28  		fmt.Fprintln(w, "// Copyright 2011 The Go Authors. All rights reserved.")
    29  		fmt.Fprintln(w, "// Use of this source code is governed by a BSD-style")
    30  		fmt.Fprintln(w, "// license that can be found in the LICENSE file.")
    31  		fmt.Fprintln(w)
    32  		fmt.Fprintln(w, "// Code generated by 'go test cmd/go -v -run=^TestDocsUpToDate$ -fixdocs'; DO NOT EDIT.")
    33  		fmt.Fprintln(w, "// Edit the documentation in other files and then execute 'go generate cmd/go' to generate this one.")
    34  		fmt.Fprintln(w)
    35  		buf := new(strings.Builder)
    36  		PrintUsage(buf, base.Go)
    37  		usage := &base.Command{Long: buf.String()}
    38  		cmds := []*base.Command{usage}
    39  		for _, cmd := range base.Go.Commands {
    40  			cmds = append(cmds, cmd)
    41  			cmds = append(cmds, cmd.Commands...)
    42  		}
    43  		tmpl(&commentWriter{W: w}, documentationTemplate, cmds)
    44  		fmt.Fprintln(w, "package main")
    45  		return
    46  	}
    47  
    48  	cmd := base.Go
    49  Args:
    50  	for i, arg := range args {
    51  		for _, sub := range cmd.Commands {
    52  			if sub.Name() == arg {
    53  				cmd = sub
    54  				continue Args
    55  			}
    56  		}
    57  
    58  		// helpSuccess is the help command using as many args as possible that would succeed.
    59  		helpSuccess := "go help"
    60  		if i > 0 {
    61  			helpSuccess += " " + strings.Join(args[:i], " ")
    62  		}
    63  		counterErrorsHelpUnknownTopic.Inc()
    64  		fmt.Fprintf(os.Stderr, "go help %s: unknown help topic. Run '%s'.\n", strings.Join(args, " "), helpSuccess)
    65  		base.SetExitStatus(2) // failed at 'go help cmd'
    66  		base.Exit()
    67  	}
    68  
    69  	if len(cmd.Commands) > 0 {
    70  		PrintUsage(os.Stdout, cmd)
    71  	} else {
    72  		tmpl(os.Stdout, helpTemplate, cmd)
    73  	}
    74  	// not exit 2: succeeded at 'go help cmd'.
    75  	return
    76  }
    77  
    78  var usageTemplate = `{{.Long | trim}}
    79  
    80  Usage:
    81  
    82  	{{.UsageLine}} <command> [arguments]
    83  
    84  The commands are:
    85  {{range .Commands}}{{if or (.Runnable) .Commands}}
    86  	{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
    87  
    88  Use "go help{{with .LongName}} {{.}}{{end}} <command>" for more information about a command.
    89  {{if eq (.UsageLine) "go"}}
    90  Additional help topics:
    91  {{range .Commands}}{{if and (not .Runnable) (not .Commands)}}
    92  	{{.Name | printf "%-15s"}} {{.Short}}{{end}}{{end}}
    93  
    94  Use "go help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
    95  {{end}}
    96  `
    97  
    98  var helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}
    99  
   100  {{end}}{{.Long | trim}}
   101  `
   102  
   103  var documentationTemplate = `{{range .}}{{if .Short}}{{.Short | capitalize}}
   104  
   105  {{end}}{{if .Commands}}` + usageTemplate + `{{else}}{{if .Runnable}}Usage:
   106  
   107  	{{.UsageLine}}
   108  
   109  {{end}}{{.Long | trim}}
   110  
   111  
   112  {{end}}{{end}}`
   113  
   114  // commentWriter writes a Go comment to the underlying io.Writer,
   115  // using line comment form (//).
   116  type commentWriter struct {
   117  	W            io.Writer
   118  	wroteSlashes bool // Wrote "//" at the beginning of the current line.
   119  }
   120  
   121  func (c *commentWriter) Write(p []byte) (int, error) {
   122  	var n int
   123  	for i, b := range p {
   124  		if !c.wroteSlashes {
   125  			s := "//"
   126  			if b != '\n' {
   127  				s = "// "
   128  			}
   129  			if _, err := io.WriteString(c.W, s); err != nil {
   130  				return n, err
   131  			}
   132  			c.wroteSlashes = true
   133  		}
   134  		n0, err := c.W.Write(p[i : i+1])
   135  		n += n0
   136  		if err != nil {
   137  			return n, err
   138  		}
   139  		if b == '\n' {
   140  			c.wroteSlashes = false
   141  		}
   142  	}
   143  	return len(p), nil
   144  }
   145  
   146  // An errWriter wraps a writer, recording whether a write error occurred.
   147  type errWriter struct {
   148  	w   io.Writer
   149  	err error
   150  }
   151  
   152  func (w *errWriter) Write(b []byte) (int, error) {
   153  	n, err := w.w.Write(b)
   154  	if err != nil {
   155  		w.err = err
   156  	}
   157  	return n, err
   158  }
   159  
   160  // tmpl executes the given template text on data, writing the result to w.
   161  func tmpl(w io.Writer, text string, data any) {
   162  	t := template.New("top")
   163  	t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
   164  	template.Must(t.Parse(text))
   165  	ew := &errWriter{w: w}
   166  	err := t.Execute(ew, data)
   167  	if ew.err != nil {
   168  		// I/O error writing. Ignore write on closed pipe.
   169  		if strings.Contains(ew.err.Error(), "pipe") {
   170  			base.SetExitStatus(1)
   171  			base.Exit()
   172  		}
   173  		base.Fatalf("writing output: %v", ew.err)
   174  	}
   175  	if err != nil {
   176  		panic(err)
   177  	}
   178  }
   179  
   180  func capitalize(s string) string {
   181  	if s == "" {
   182  		return s
   183  	}
   184  	r, n := utf8.DecodeRuneInString(s)
   185  	return string(unicode.ToTitle(r)) + s[n:]
   186  }
   187  
   188  func PrintUsage(w io.Writer, cmd *base.Command) {
   189  	bw := bufio.NewWriter(w)
   190  	tmpl(bw, usageTemplate, cmd)
   191  	bw.Flush()
   192  }
   193  

View as plain text