github.com/huandu/go@v0.0.0-20151114150818-04e615e41150/src/cmd/doc/main.go (about)

     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>[.<method>]
    14  //	go doc [<pkg>].<sym>[.<method>]
    15  // The first item in this list that succeeds is the one whose documentation
    16  // is printed. If there is a symbol but no package, the package in the current
    17  // directory is chosen.
    18  //
    19  // Two arguments:
    20  //	go doc <pkg> <sym>[.<method>]
    21  //
    22  // Show the documentation for the package, symbol, and method. The
    23  // first argument must be a full package path. This is similar to the
    24  // command-line usage for the godoc command.
    25  //
    26  // For commands, unless the -cmd flag is present "go doc command"
    27  // shows only the package-level docs for the package.
    28  //
    29  // For complete documentation, run "go help doc".
    30  package main
    31  
    32  import (
    33  	"flag"
    34  	"fmt"
    35  	"go/build"
    36  	"io"
    37  	"log"
    38  	"os"
    39  	"path"
    40  	"path/filepath"
    41  	"strings"
    42  	"unicode"
    43  	"unicode/utf8"
    44  )
    45  
    46  var (
    47  	unexported bool // -u flag
    48  	matchCase  bool // -c flag
    49  	showCmd    bool // -cmd flag
    50  )
    51  
    52  // usage is a replacement usage function for the flags package.
    53  func usage() {
    54  	fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n")
    55  	fmt.Fprintf(os.Stderr, "\tgo doc\n")
    56  	fmt.Fprintf(os.Stderr, "\tgo doc <pkg>\n")
    57  	fmt.Fprintf(os.Stderr, "\tgo doc <sym>[.<method>]\n")
    58  	fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>].<sym>[.<method>]\n")
    59  	fmt.Fprintf(os.Stderr, "\tgo doc <pkg> <sym>[.<method>]\n")
    60  	fmt.Fprintf(os.Stderr, "For more information run\n")
    61  	fmt.Fprintf(os.Stderr, "\tgo help doc\n\n")
    62  	fmt.Fprintf(os.Stderr, "Flags:\n")
    63  	flag.PrintDefaults()
    64  	os.Exit(2)
    65  }
    66  
    67  func main() {
    68  	log.SetFlags(0)
    69  	log.SetPrefix("doc: ")
    70  	err := do(os.Stdout, flag.CommandLine, os.Args[1:])
    71  	if err != nil {
    72  		log.Fatal(err)
    73  	}
    74  }
    75  
    76  // do is the workhorse, broken out of main to make testing easier.
    77  func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
    78  	flagSet.Usage = usage
    79  	unexported = false
    80  	matchCase = false
    81  	flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported")
    82  	flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)")
    83  	flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command")
    84  	flagSet.Parse(args)
    85  	buildPackage, userPath, symbol := parseArgs(flagSet.Args())
    86  	symbol, method := parseSymbol(symbol)
    87  	pkg := parsePackage(writer, buildPackage, userPath)
    88  	defer func() {
    89  		pkg.flush()
    90  		e := recover()
    91  		if e == nil {
    92  			return
    93  		}
    94  		pkgError, ok := e.(PackageError)
    95  		if ok {
    96  			err = pkgError
    97  			return
    98  		}
    99  		panic(e)
   100  	}()
   101  	switch {
   102  	case symbol == "":
   103  		pkg.packageDoc()
   104  		return
   105  	case method == "":
   106  		pkg.symbolDoc(symbol)
   107  	default:
   108  		pkg.methodDoc(symbol, method)
   109  	}
   110  	return nil
   111  }
   112  
   113  // parseArgs analyzes the arguments (if any) and returns the package
   114  // it represents, the part of the argument the user used to identify
   115  // the path (or "" if it's the current package) and the symbol
   116  // (possibly with a .method) within that package.
   117  // parseSymbol is used to analyze the symbol itself.
   118  func parseArgs(args []string) (*build.Package, string, string) {
   119  	switch len(args) {
   120  	default:
   121  		usage()
   122  	case 0:
   123  		// Easy: current directory.
   124  		return importDir(pwd()), "", ""
   125  	case 1:
   126  		// Done below.
   127  	case 2:
   128  		// Package must be importable.
   129  		pkg, err := build.Import(args[0], "", build.ImportComment)
   130  		if err != nil {
   131  			log.Fatalf("%s", err)
   132  		}
   133  		return pkg, args[0], args[1]
   134  	}
   135  	// Usual case: one argument.
   136  	arg := args[0]
   137  	// If it contains slashes, it begins with a package path.
   138  	// First, is it a complete package path as it is? If so, we are done.
   139  	// This avoids confusion over package paths that have other
   140  	// package paths as their prefix.
   141  	pkg, err := build.Import(arg, "", build.ImportComment)
   142  	if err == nil {
   143  		return pkg, arg, ""
   144  	}
   145  	// Another disambiguator: If the symbol starts with an upper
   146  	// case letter, it can only be a symbol in the current directory.
   147  	// Kills the problem caused by case-insensitive file systems
   148  	// matching an upper case name as a package name.
   149  	if isUpper(arg) {
   150  		pkg, err := build.ImportDir(".", build.ImportComment)
   151  		if err == nil {
   152  			return pkg, "", arg
   153  		}
   154  	}
   155  	// If it has a slash, it must be a package path but there is a symbol.
   156  	// It's the last package path we care about.
   157  	slash := strings.LastIndex(arg, "/")
   158  	// There may be periods in the package path before or after the slash
   159  	// and between a symbol and method.
   160  	// Split the string at various periods to see what we find.
   161  	// In general there may be ambiguities but this should almost always
   162  	// work.
   163  	var period int
   164  	// slash+1: if there's no slash, the value is -1 and start is 0; otherwise
   165  	// start is the byte after the slash.
   166  	for start := slash + 1; start < len(arg); start = period + 1 {
   167  		period = strings.Index(arg[start:], ".")
   168  		symbol := ""
   169  		if period < 0 {
   170  			period = len(arg)
   171  		} else {
   172  			period += start
   173  			symbol = arg[period+1:]
   174  		}
   175  		// Have we identified a package already?
   176  		pkg, err := build.Import(arg[0:period], "", build.ImportComment)
   177  		if err == nil {
   178  			return pkg, arg[0:period], symbol
   179  		}
   180  		// See if we have the basename or tail of a package, as in json for encoding/json
   181  		// or ivy/value for robpike.io/ivy/value.
   182  		path := findPackage(arg[0:period])
   183  		if path != "" {
   184  			return importDir(path), arg[0:period], symbol
   185  		}
   186  	}
   187  	// If it has a slash, we've failed.
   188  	if slash >= 0 {
   189  		log.Fatalf("no such package %s", arg[0:period])
   190  	}
   191  	// Guess it's a symbol in the current directory.
   192  	return importDir(pwd()), "", arg
   193  }
   194  
   195  // importDir is just an error-catching wrapper for build.ImportDir.
   196  func importDir(dir string) *build.Package {
   197  	pkg, err := build.ImportDir(dir, build.ImportComment)
   198  	if err != nil {
   199  		log.Fatal(err)
   200  	}
   201  	return pkg
   202  }
   203  
   204  // parseSymbol breaks str apart into a symbol and method.
   205  // Both may be missing or the method may be missing.
   206  // If present, each must be a valid Go identifier.
   207  func parseSymbol(str string) (symbol, method string) {
   208  	if str == "" {
   209  		return
   210  	}
   211  	elem := strings.Split(str, ".")
   212  	switch len(elem) {
   213  	case 1:
   214  	case 2:
   215  		method = elem[1]
   216  		isIdentifier(method)
   217  	default:
   218  		log.Printf("too many periods in symbol specification")
   219  		usage()
   220  	}
   221  	symbol = elem[0]
   222  	isIdentifier(symbol)
   223  	return
   224  }
   225  
   226  // isIdentifier checks that the name is valid Go identifier, and
   227  // logs and exits if it is not.
   228  func isIdentifier(name string) {
   229  	if len(name) == 0 {
   230  		log.Fatal("empty symbol")
   231  	}
   232  	for i, ch := range name {
   233  		if unicode.IsLetter(ch) || ch == '_' || i > 0 && unicode.IsDigit(ch) {
   234  			continue
   235  		}
   236  		log.Fatalf("invalid identifier %q", name)
   237  	}
   238  }
   239  
   240  // isExported reports whether the name is an exported identifier.
   241  // If the unexported flag (-u) is true, isExported returns true because
   242  // it means that we treat the name as if it is exported.
   243  func isExported(name string) bool {
   244  	return unexported || isUpper(name)
   245  }
   246  
   247  // isUpper reports whether the name starts with an upper case letter.
   248  func isUpper(name string) bool {
   249  	ch, _ := utf8.DecodeRuneInString(name)
   250  	return unicode.IsUpper(ch)
   251  }
   252  
   253  // findPackage returns the full file name path specified by the
   254  // (perhaps partial) package path pkg.
   255  func findPackage(pkg string) string {
   256  	if pkg == "" {
   257  		return ""
   258  	}
   259  	if isUpper(pkg) {
   260  		return "" // Upper case symbol cannot be a package name.
   261  	}
   262  	path := pathFor(build.Default.GOROOT, pkg)
   263  	if path != "" {
   264  		return path
   265  	}
   266  	for _, root := range splitGopath() {
   267  		path = pathFor(root, pkg)
   268  		if path != "" {
   269  			return path
   270  		}
   271  	}
   272  	return ""
   273  }
   274  
   275  // splitGopath splits $GOPATH into a list of roots.
   276  func splitGopath() []string {
   277  	return filepath.SplitList(build.Default.GOPATH)
   278  }
   279  
   280  // pathsFor recursively walks the tree at root looking for possible directories for the package:
   281  // those whose package path is pkg or which have a proper suffix pkg.
   282  func pathFor(root, pkg string) (result string) {
   283  	root = path.Join(root, "src")
   284  	slashDot := string(filepath.Separator) + "."
   285  	// We put a slash on the pkg so can use simple string comparison below
   286  	// yet avoid inadvertent matches, like /foobar matching bar.
   287  	pkgString := filepath.Clean(string(filepath.Separator) + pkg)
   288  
   289  	// We use panic/defer to short-circuit processing at the first match.
   290  	// A nil panic reports that the path has been found.
   291  	defer func() {
   292  		err := recover()
   293  		if err != nil {
   294  			panic(err)
   295  		}
   296  	}()
   297  
   298  	visit := func(pathName string, f os.FileInfo, err error) error {
   299  		if err != nil {
   300  			return nil
   301  		}
   302  		// One package per directory. Ignore the files themselves.
   303  		if !f.IsDir() {
   304  			return nil
   305  		}
   306  		// No .git or other dot nonsense please.
   307  		if strings.Contains(pathName, slashDot) {
   308  			return filepath.SkipDir
   309  		}
   310  		// Is the tail of the path correct?
   311  		if strings.HasSuffix(pathName, pkgString) && hasGoFiles(pathName) {
   312  			result = pathName
   313  			panic(nil)
   314  		}
   315  		return nil
   316  	}
   317  
   318  	filepath.Walk(root, visit)
   319  	return "" // Call to panic above sets the real value.
   320  }
   321  
   322  // hasGoFiles tests whether the directory contains at least one file with ".go"
   323  // extension
   324  func hasGoFiles(path string) bool {
   325  	dir, err := os.Open(path)
   326  	if err != nil {
   327  		// ignore unreadable directories
   328  		return false
   329  	}
   330  	defer dir.Close()
   331  
   332  	names, err := dir.Readdirnames(0)
   333  	if err != nil {
   334  		// ignore unreadable directories
   335  		return false
   336  	}
   337  
   338  	for _, name := range names {
   339  		if strings.HasSuffix(name, ".go") {
   340  			return true
   341  		}
   342  	}
   343  
   344  	return false
   345  }
   346  
   347  // pwd returns the current directory.
   348  func pwd() string {
   349  	wd, err := os.Getwd()
   350  	if err != nil {
   351  		log.Fatal(err)
   352  	}
   353  	return wd
   354  }