...
Run Format

Source file src/cmd/doc/main.go

Documentation: cmd/doc

     1  // Copyright 2015 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  // Doc (usually run as go doc) accepts zero, one or two arguments.
     6  //
     7  // Zero arguments:
     8  //	go doc
     9  // Show the documentation for the package in the current directory.
    10  //
    11  // One argument:
    12  //	go doc <pkg>
    13  //	go doc <sym>[.<methodOrField>]
    14  //	go doc [<pkg>.]<sym>[.<methodOrField>]
    15  //	go doc [<pkg>.][<sym>.]<methodOrField>
    16  // The first item in this list that succeeds is the one whose documentation
    17  // is printed. If there is a symbol but no package, the package in the current
    18  // directory is chosen. However, if the argument begins with a capital
    19  // letter it is always assumed to be a symbol in the current directory.
    20  //
    21  // Two arguments:
    22  //	go doc <pkg> <sym>[.<methodOrField>]
    23  //
    24  // Show the documentation for the package, symbol, and method or field. The
    25  // first argument must be a full package path. This is similar to the
    26  // command-line usage for the godoc command.
    27  //
    28  // For commands, unless the -cmd flag is present "go doc command"
    29  // shows only the package-level docs for the package.
    30  //
    31  // For complete documentation, run "go help doc".
    32  package main
    33  
    34  import (
    35  	"bytes"
    36  	"flag"
    37  	"fmt"
    38  	"go/build"
    39  	"io"
    40  	"log"
    41  	"os"
    42  	"path"
    43  	"path/filepath"
    44  	"strings"
    45  	"unicode"
    46  	"unicode/utf8"
    47  )
    48  
    49  var (
    50  	unexported bool // -u flag
    51  	matchCase  bool // -c flag
    52  	showCmd    bool // -cmd flag
    53  )
    54  
    55  // usage is a replacement usage function for the flags package.
    56  func usage() {
    57  	fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n")
    58  	fmt.Fprintf(os.Stderr, "\tgo doc\n")
    59  	fmt.Fprintf(os.Stderr, "\tgo doc <pkg>\n")
    60  	fmt.Fprintf(os.Stderr, "\tgo doc <sym>[.<method>]\n")
    61  	fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>].<sym>[.<method>]\n")
    62  	fmt.Fprintf(os.Stderr, "\tgo doc <pkg> <sym>[.<method>]\n")
    63  	fmt.Fprintf(os.Stderr, "For more information run\n")
    64  	fmt.Fprintf(os.Stderr, "\tgo help doc\n\n")
    65  	fmt.Fprintf(os.Stderr, "Flags:\n")
    66  	flag.PrintDefaults()
    67  	os.Exit(2)
    68  }
    69  
    70  func main() {
    71  	log.SetFlags(0)
    72  	log.SetPrefix("doc: ")
    73  	dirsInit()
    74  	err := do(os.Stdout, flag.CommandLine, os.Args[1:])
    75  	if err != nil {
    76  		log.Fatal(err)
    77  	}
    78  }
    79  
    80  // do is the workhorse, broken out of main to make testing easier.
    81  func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
    82  	flagSet.Usage = usage
    83  	unexported = false
    84  	matchCase = false
    85  	flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported")
    86  	flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)")
    87  	flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command")
    88  	flagSet.Parse(args)
    89  	var paths []string
    90  	var symbol, method string
    91  	// Loop until something is printed.
    92  	dirs.Reset()
    93  	for i := 0; ; i++ {
    94  		buildPackage, userPath, sym, more := parseArgs(flagSet.Args())
    95  		if i > 0 && !more { // Ignore the "more" bit on the first iteration.
    96  			return failMessage(paths, symbol, method)
    97  		}
    98  		if buildPackage == nil {
    99  			return fmt.Errorf("no such package: %s", userPath)
   100  		}
   101  		symbol, method = parseSymbol(sym)
   102  		pkg := parsePackage(writer, buildPackage, userPath)
   103  		paths = append(paths, pkg.prettyPath())
   104  
   105  		defer func() {
   106  			pkg.flush()
   107  			e := recover()
   108  			if e == nil {
   109  				return
   110  			}
   111  			pkgError, ok := e.(PackageError)
   112  			if ok {
   113  				err = pkgError
   114  				return
   115  			}
   116  			panic(e)
   117  		}()
   118  
   119  		// The builtin package needs special treatment: its symbols are lower
   120  		// case but we want to see them, always.
   121  		if pkg.build.ImportPath == "builtin" {
   122  			unexported = true
   123  		}
   124  
   125  		switch {
   126  		case symbol == "":
   127  			pkg.packageDoc() // The package exists, so we got some output.
   128  			return
   129  		case method == "":
   130  			if pkg.symbolDoc(symbol) {
   131  				return
   132  			}
   133  		default:
   134  			if pkg.methodDoc(symbol, method) {
   135  				return
   136  			}
   137  			if pkg.fieldDoc(symbol, method) {
   138  				return
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  // failMessage creates a nicely formatted error message when there is no result to show.
   145  func failMessage(paths []string, symbol, method string) error {
   146  	var b bytes.Buffer
   147  	if len(paths) > 1 {
   148  		b.WriteString("s")
   149  	}
   150  	b.WriteString(" ")
   151  	for i, path := range paths {
   152  		if i > 0 {
   153  			b.WriteString(", ")
   154  		}
   155  		b.WriteString(path)
   156  	}
   157  	if method == "" {
   158  		return fmt.Errorf("no symbol %s in package%s", symbol, &b)
   159  	}
   160  	return fmt.Errorf("no method or field %s.%s in package%s", symbol, method, &b)
   161  }
   162  
   163  // parseArgs analyzes the arguments (if any) and returns the package
   164  // it represents, the part of the argument the user used to identify
   165  // the path (or "" if it's the current package) and the symbol
   166  // (possibly with a .method) within that package.
   167  // parseSymbol is used to analyze the symbol itself.
   168  // The boolean final argument reports whether it is possible that
   169  // there may be more directories worth looking at. It will only
   170  // be true if the package path is a partial match for some directory
   171  // and there may be more matches. For example, if the argument
   172  // is rand.Float64, we must scan both crypto/rand and math/rand
   173  // to find the symbol, and the first call will return crypto/rand, true.
   174  func parseArgs(args []string) (pkg *build.Package, path, symbol string, more bool) {
   175  	if len(args) == 0 {
   176  		// Easy: current directory.
   177  		return importDir(pwd()), "", "", false
   178  	}
   179  	arg := args[0]
   180  	// We have an argument. If it is a directory name beginning with . or ..,
   181  	// use the absolute path name. This discriminates "./errors" from "errors"
   182  	// if the current directory contains a non-standard errors package.
   183  	if isDotSlash(arg) {
   184  		arg = filepath.Join(pwd(), arg)
   185  	}
   186  	switch len(args) {
   187  	default:
   188  		usage()
   189  	case 1:
   190  		// Done below.
   191  	case 2:
   192  		// Package must be findable and importable.
   193  		pkg, err := build.Import(args[0], "", build.ImportComment)
   194  		if err == nil {
   195  			return pkg, args[0], args[1], false
   196  		}
   197  		for {
   198  			packagePath, ok := findNextPackage(arg)
   199  			if !ok {
   200  				break
   201  			}
   202  			if pkg, err := build.ImportDir(packagePath, build.ImportComment); err == nil {
   203  				return pkg, arg, args[1], true
   204  			}
   205  		}
   206  		return nil, args[0], args[1], false
   207  	}
   208  	// Usual case: one argument.
   209  	// If it contains slashes, it begins with a package path.
   210  	// First, is it a complete package path as it is? If so, we are done.
   211  	// This avoids confusion over package paths that have other
   212  	// package paths as their prefix.
   213  	pkg, err := build.Import(arg, "", build.ImportComment)
   214  	if err == nil {
   215  		return pkg, arg, "", false
   216  	}
   217  	// Another disambiguator: If the symbol starts with an upper
   218  	// case letter, it can only be a symbol in the current directory.
   219  	// Kills the problem caused by case-insensitive file systems
   220  	// matching an upper case name as a package name.
   221  	if isUpper(arg) {
   222  		pkg, err := build.ImportDir(".", build.ImportComment)
   223  		if err == nil {
   224  			return pkg, "", arg, false
   225  		}
   226  	}
   227  	// If it has a slash, it must be a package path but there is a symbol.
   228  	// It's the last package path we care about.
   229  	slash := strings.LastIndex(arg, "/")
   230  	// There may be periods in the package path before or after the slash
   231  	// and between a symbol and method.
   232  	// Split the string at various periods to see what we find.
   233  	// In general there may be ambiguities but this should almost always
   234  	// work.
   235  	var period int
   236  	// slash+1: if there's no slash, the value is -1 and start is 0; otherwise
   237  	// start is the byte after the slash.
   238  	for start := slash + 1; start < len(arg); start = period + 1 {
   239  		period = strings.Index(arg[start:], ".")
   240  		symbol := ""
   241  		if period < 0 {
   242  			period = len(arg)
   243  		} else {
   244  			period += start
   245  			symbol = arg[period+1:]
   246  		}
   247  		// Have we identified a package already?
   248  		pkg, err := build.Import(arg[0:period], "", build.ImportComment)
   249  		if err == nil {
   250  			return pkg, arg[0:period], symbol, false
   251  		}
   252  		// See if we have the basename or tail of a package, as in json for encoding/json
   253  		// or ivy/value for robpike.io/ivy/value.
   254  		pkgName := arg[:period]
   255  		for {
   256  			path, ok := findNextPackage(pkgName)
   257  			if !ok {
   258  				break
   259  			}
   260  			if pkg, err = build.ImportDir(path, build.ImportComment); err == nil {
   261  				return pkg, arg[0:period], symbol, true
   262  			}
   263  		}
   264  		dirs.Reset() // Next iteration of for loop must scan all the directories again.
   265  	}
   266  	// If it has a slash, we've failed.
   267  	if slash >= 0 {
   268  		log.Fatalf("no such package %s", arg[0:period])
   269  	}
   270  	// Guess it's a symbol in the current directory.
   271  	return importDir(pwd()), "", arg, false
   272  }
   273  
   274  // dotPaths lists all the dotted paths legal on Unix-like and
   275  // Windows-like file systems. We check them all, as the chance
   276  // of error is minute and even on Windows people will use ./
   277  // sometimes.
   278  var dotPaths = []string{
   279  	`./`,
   280  	`../`,
   281  	`.\`,
   282  	`..\`,
   283  }
   284  
   285  // isDotSlash reports whether the path begins with a reference
   286  // to the local . or .. directory.
   287  func isDotSlash(arg string) bool {
   288  	if arg == "." || arg == ".." {
   289  		return true
   290  	}
   291  	for _, dotPath := range dotPaths {
   292  		if strings.HasPrefix(arg, dotPath) {
   293  			return true
   294  		}
   295  	}
   296  	return false
   297  }
   298  
   299  // importDir is just an error-catching wrapper for build.ImportDir.
   300  func importDir(dir string) *build.Package {
   301  	pkg, err := build.ImportDir(dir, build.ImportComment)
   302  	if err != nil {
   303  		log.Fatal(err)
   304  	}
   305  	return pkg
   306  }
   307  
   308  // parseSymbol breaks str apart into a symbol and method.
   309  // Both may be missing or the method may be missing.
   310  // If present, each must be a valid Go identifier.
   311  func parseSymbol(str string) (symbol, method string) {
   312  	if str == "" {
   313  		return
   314  	}
   315  	elem := strings.Split(str, ".")
   316  	switch len(elem) {
   317  	case 1:
   318  	case 2:
   319  		method = elem[1]
   320  		isIdentifier(method)
   321  	default:
   322  		log.Printf("too many periods in symbol specification")
   323  		usage()
   324  	}
   325  	symbol = elem[0]
   326  	isIdentifier(symbol)
   327  	return
   328  }
   329  
   330  // isIdentifier checks that the name is valid Go identifier, and
   331  // logs and exits if it is not.
   332  func isIdentifier(name string) {
   333  	if len(name) == 0 {
   334  		log.Fatal("empty symbol")
   335  	}
   336  	for i, ch := range name {
   337  		if unicode.IsLetter(ch) || ch == '_' || i > 0 && unicode.IsDigit(ch) {
   338  			continue
   339  		}
   340  		log.Fatalf("invalid identifier %q", name)
   341  	}
   342  }
   343  
   344  // isExported reports whether the name is an exported identifier.
   345  // If the unexported flag (-u) is true, isExported returns true because
   346  // it means that we treat the name as if it is exported.
   347  func isExported(name string) bool {
   348  	return unexported || isUpper(name)
   349  }
   350  
   351  // isUpper reports whether the name starts with an upper case letter.
   352  func isUpper(name string) bool {
   353  	ch, _ := utf8.DecodeRuneInString(name)
   354  	return unicode.IsUpper(ch)
   355  }
   356  
   357  // findNextPackage returns the next full file name path that matches the
   358  // (perhaps partial) package path pkg. The boolean reports if any match was found.
   359  func findNextPackage(pkg string) (string, bool) {
   360  	if pkg == "" || isUpper(pkg) { // Upper case symbol cannot be a package name.
   361  		return "", false
   362  	}
   363  	if filepath.IsAbs(pkg) {
   364  		if dirs.offset == 0 {
   365  			dirs.offset = -1
   366  			return pkg, true
   367  		}
   368  		return "", false
   369  	}
   370  	pkg = path.Clean(pkg)
   371  	pkgSuffix := "/" + pkg
   372  	for {
   373  		d, ok := dirs.Next()
   374  		if !ok {
   375  			return "", false
   376  		}
   377  		if d.importPath == pkg || strings.HasSuffix(d.importPath, pkgSuffix) {
   378  			return d.dir, true
   379  		}
   380  	}
   381  }
   382  
   383  var buildCtx = build.Default
   384  
   385  // splitGopath splits $GOPATH into a list of roots.
   386  func splitGopath() []string {
   387  	return filepath.SplitList(buildCtx.GOPATH)
   388  }
   389  
   390  // pwd returns the current directory.
   391  func pwd() string {
   392  	wd, err := os.Getwd()
   393  	if err != nil {
   394  		log.Fatal(err)
   395  	}
   396  	return wd
   397  }
   398  

View as plain text