Reviewers: golang-dev_googlegroups.com,
Message:
Hello golang-dev@googlegroups.com,
I'd like you to review this change to
https://code.google.com/p/go
Description:
cmd/godoc: handle file name arguments better
This change allows you to refer to the directory "foo" relative
to current directory as "foo" or "./foo".
Furthermore, it discovers if "foo" resides under an GOPATH or GOROOT
and displays the correct import path. For example,
cd $GOROOT/src/pkg/archive
godoc zip
will display the docs for "archive/zip" with the correct import path.
Fixes issue 4330.
Please review this at https://codereview.appspot.com/7132044/
Affected files:
M src/cmd/godoc/doc.go
M src/cmd/godoc/main.go
Index: src/cmd/godoc/doc.go
===================================================================
--- a/src/cmd/godoc/doc.go
+++ b/src/cmd/godoc/doc.go
@@ -9,15 +9,20 @@
It has two modes.
Without the -http flag, it runs in command-line mode and prints plain text
-documentation to standard output and exits. If both a library package and
-a command with the same name exists, using the prefix cmd/ will force
-documentation on the command rather than the library package. If the -src
-flag is specified, godoc prints the exported interface of a package in Go
-source form, or the implementation of a specific exported language entity:
+documentation to standard output and exits.
+
+Its first non-flag argument must be an import path or an absolute or
relative
+file system path to a Go package or command. If both a standard library
package
+and a Go tool command with the same name exists, using the prefix cmd/ will
+force documentation on the command rather than the library package.
+
+If the -src flag is specified, godoc prints the exported interface of a
package
+in Go source form, or the implementation of a specific exported language
entity:
godoc fmt # documentation for package fmt
godoc fmt Printf # documentation for fmt.Printf
godoc cmd/go # force documentation for the go command
+ godoc . # docs for the package in the current directory
godoc -src fmt # fmt package interface in Go source form
godoc -src fmt Printf # implementation of fmt.Printf
Index: src/cmd/godoc/main.go
===================================================================
--- a/src/cmd/godoc/main.go
+++ b/src/cmd/godoc/main.go
@@ -339,22 +339,33 @@
var forceCmd bool
var abspath, relpath string
if filepath.IsAbs(path) {
+ // Absolute path takes precedence.
+ // (the go command invokes godoc w/ absolute paths; don't override)
fs.Bind(target, OS(path), "/", bindReplace)
abspath = target
- } else if build.IsLocalImport(path) {
+ } else if strings.HasPrefix(path, cmdPrefix) {
+ // Path "cmd/foo" refers to go command "foo".
+ path = path[len(cmdPrefix):]
+ forceCmd = true
+ } else if bp, _ := build.Import(path, "",
build.FindOnly); !build.IsLocalImport(path) && bp.Dir != "" &&
bp.ImportPath != "" {
+ // Packages in $GOROOT/src/pkg or GOPATH directories.
+ fs.Bind(target, OS(bp.Dir), "/", bindReplace)
+ abspath = target
+ relpath = bp.ImportPath
+ } else if fi, err := os.Stat(path); err == nil && fi.IsDir() {
+ // Path is a directory relative to the CWD.
cwd, _ := os.Getwd() // ignore errors
path = filepath.Join(cwd, path)
fs.Bind(target, OS(path), "/", bindReplace)
abspath = target
- } else if strings.HasPrefix(path, cmdPrefix) {
- path = path[len(cmdPrefix):]
- forceCmd = true
- } else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != ""
&& bp.ImportPath != "" {
- fs.Bind(target, OS(bp.Dir), "/", bindReplace)
- abspath = target
- relpath = bp.ImportPath
- } else {
- abspath = pathpkg.Join(pkgHandler.fsRoot, path)
+ relpath = path
+ // If the directory is inside GOPATH,
+ // use its actual import path as relpath.
+ for _, root := range build.Default.SrcDirs() {
+ if p, ok := hasSubdir(root, path); ok {
+ relpath = p
+ }
+ }
}
if relpath == "" {
relpath = abspath
@@ -373,37 +384,41 @@
mode |= showSource
}
- // first, try as package unless forced as command
+ // First, try as package unless forced as command.
var info PageInfo
if !forceCmd {
info = pkgHandler.getPageInfo(abspath, relpath, mode)
}
- // second, try as command unless the path is absolute
- // (the go command invokes godoc w/ absolute paths; don't override)
+ // Also try as command if path is not absolute.
var cinfo PageInfo
if !filepath.IsAbs(path) {
abspath = pathpkg.Join(cmdHandler.fsRoot, path)
cinfo = cmdHandler.getPageInfo(abspath, relpath, mode)
}
- // determine what to use
+ // Choose whether to use info or cinfo.
if info.IsEmpty() {
- if !cinfo.IsEmpty() {
- // only cinfo exists - switch to cinfo
- info = cinfo
- }
+ // info doesn't exist - use cinfo.
+ info = cinfo
} else if !cinfo.IsEmpty() {
- // both info and cinfo exist - use cinfo if info
- // contains only subdirectory information
+ // Both info and cinfo exist.
if info.PAst == nil && info.PDoc == nil {
+ // info contains only subdirectory information - use cinfo.
+ // (Choose cmd/go over pkg/go for path "go".)
info = cinfo
} else {
+ // Both info and cinfo contain docs - use info.
+ // But tell user how to get the command docs.
fmt.Printf("use 'godoc %s%s' for documentation on the %s command \n\n",
cmdPrefix, relpath, relpath)
}
}
if info.Err != nil {
+ if os.IsNotExist(info.Err) {
+ // Show a friendlier error than the one returned by os.Open.
+ log.Fatalf("package not found: %v", path)
+ }
log.Fatalf("%v", info.Err)
}
if info.PDoc != nil && info.PDoc.ImportPath == target {
@@ -455,6 +470,29 @@
}
}
+// hasSubdir reports whether dir is a subdirectory of
+// (perhaps multiple levels below) root.
+// If so, hasSubdir sets rel to a slash-separated path that
+// can be joined to root to produce a path equivalent to dir.
+func hasSubdir(root, dir string) (rel string, ok bool) {
+ if p, err := filepath.EvalSymlinks(root); err == nil {
+ root = p
+ }
+ if p, err := filepath.EvalSymlinks(dir); err == nil {
+ dir = p
+ }
+ const sep = string(filepath.Separator)
+ root = filepath.Clean(root)
+ if !strings.HasSuffix(root, sep) {
+ root += sep
+ }
+ dir = filepath.Clean(dir)
+ if !strings.HasPrefix(dir, root) {
+ return "", false
+ }
+ return filepath.ToSlash(dir[len(root):]), true
+}
+
// An httpWriter is an http.ResponseWriter writing to a bytes.Buffer.
type httpWriter struct {
bytes.Buffer