...

Source file src/cmd/doc/main.go

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

View as plain text