cmd/doc: don't stop after first package if the symbol is not found

The test case is
	go doc rand.Float64
The first package it finds is crypto/rand, which does not have a Float64.
Before this change, cmd/doc would stop there even though math/rand
has the symbol. After this change, we get:

	% go doc rand.Float64
	package rand // import "math/rand"

	func Float64() float64

	    Float64 returns, as a float64, a pseudo-random number in [0.0,1.0) from the
	    default Source.
	%

Another nice consequence is that if a symbol is not found, we might get
a longer list of packages that were examined:

	% go doc rand.Int64
	doc: no symbol Int64 in packages crypto/rand, math/rand
	exit status 1
	%

This change introduces a coroutine to scan the file system so that if
the symbol is not found, the coroutine can deliver another path to try.
(This is darned close to the original motivation for coroutines.)
Paths are delivered on an unbuffered channel so the scanner does
not proceed until candidate paths are needed.

The scanner is attached to a new type, called Dirs, that caches the results
so if we need to scan a second time, we don't walk the file system
again. This is significantly more efficient than the existing code, which
could scan the tree multiple times looking for a package with
the symbol.

Change-Id: I2789505b9992cf04c19376c51ae09af3bc305f7f
Reviewed-on: https://go-review.googlesource.com/14921
Reviewed-by: Andrew Gerrand <adg@golang.org>
This commit is contained in:
Rob Pike 2015-09-23 16:49:30 -07:00
parent 3f7c3e01db
commit 007fa019a3
4 changed files with 310 additions and 141 deletions

View file

@ -16,6 +16,8 @@ import (
"io"
"log"
"os"
"path/filepath"
"strings"
"unicode"
"unicode/utf8"
)
@ -46,6 +48,33 @@ func (p PackageError) Error() string {
return string(p)
}
// prettyPath returns a version of the package path that is suitable for an
// error message. It obeys the import comment if present. Also, since
// pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a
// directory name in GOROOT or GOPATH if that happens.
func (pkg *Package) prettyPath() string {
path := pkg.build.ImportComment
if path == "" {
path = pkg.build.ImportPath
}
if path != "." && path != "" {
return path
}
// Conver the source directory into a more useful path.
path = filepath.Clean(pkg.build.Dir)
// Can we find a decent prefix?
goroot := filepath.Join(build.Default.GOROOT, "src")
if strings.HasPrefix(path, goroot) {
return path[len(goroot)+1:]
}
for _, gopath := range splitGopath() {
if strings.HasPrefix(path, gopath) {
return path[len(gopath)+1:]
}
}
return path
}
// pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the
// main do function, so it doesn't cause an exit. Allows testing to work
// without running a subprocess. The log prefix will be added when
@ -344,7 +373,7 @@ func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec
// symbolDoc prints the docs for symbol. There may be multiple matches.
// If symbol matches a type, output includes its methods factories and associated constants.
// If there is no top-level symbol, symbolDoc looks for methods that match.
func (pkg *Package) symbolDoc(symbol string) {
func (pkg *Package) symbolDoc(symbol string) bool {
defer pkg.flush()
found := false
// Functions.
@ -413,9 +442,10 @@ func (pkg *Package) symbolDoc(symbol string) {
if !found {
// See if there are methods.
if !pkg.printMethodDoc("", symbol) {
log.Printf("symbol %s not present in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
return false
}
}
return true
}
// trimUnexportedElems modifies spec in place to elide unexported fields from
@ -493,11 +523,9 @@ func (pkg *Package) printMethodDoc(symbol, method string) bool {
}
// methodDoc prints the docs for matches of symbol.method.
func (pkg *Package) methodDoc(symbol, method string) {
func (pkg *Package) methodDoc(symbol, method string) bool {
defer pkg.flush()
if !pkg.printMethodDoc(symbol, method) {
pkg.Fatalf("no method %s.%s in package %s installed in %q", symbol, method, pkg.name, pkg.build.ImportPath)
}
return pkg.printMethodDoc(symbol, method)
}
// match reports whether the user's symbol matches the program's.