Print it out much like godoc so there isn't a single block of text.
Print the symbol before its comment and indent the comment so
individual symbols separate visually.
Buffer the output.
Add a -c option to force case-sensitive matching.
Allow two arguments, like godoc, to help disambiguate cases
where path and symbol may be confused.
Improve the documentation printed by go help doc.
Change-Id: If687aad04bbacdf7dbe4bf7636de9fe96f756fd0
Reviewed-on: https://go-review.googlesource.com/9471
Reviewed-by: Russ Cox <rsc@golang.org>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Doc (usually run as go doc) accepts zero or one argument, interpreted as:
+// Doc (usually run as go doc) accepts zero, one or two arguments.
+//
+// Zero arguments:
// go doc
+// Show the documentation for the package in the current directory.
+//
+// One argument:
// go doc <pkg>
// go doc <sym>[.<method>]
// go doc [<pkg>].<sym>[.<method>]
// The first item in this list that succeeds is the one whose documentation
-// is printed. If there is no argument, the package in the current directory
-// is chosen.
+// is printed. If there is a symbol but no package, the package in the current
+// directory is chosen.
+//
+// Two arguments:
+// go doc <pkg> <sym>[.<method>]
+//
+// Show the documentation for the package, symbol, and method. The
+// first argument must be a full package path. This is similar to the
+// command-line usage for the godoc command.
//
// For complete documentation, run "go help doc".
package main
)
var (
- unexported bool
+ unexported = flag.Bool("u", false, "show unexported symbols as well as exported")
+ matchCase = flag.Bool("c", false, "symbol matching honors case (paths not affected)")
)
-func init() {
- flag.BoolVar(&unexported, "unexported", false, "show unexported symbols as well as exported")
- flag.BoolVar(&unexported, "u", false, "shorthand for -unexported")
-}
-
// usage is a replacement usage function for the flags package.
func usage() {
fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n")
fmt.Fprintf(os.Stderr, "\tgo doc <pkg>\n")
fmt.Fprintf(os.Stderr, "\tgo doc <sym>[.<method>]\n")
fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>].<sym>[.<method>]\n")
+ fmt.Fprintf(os.Stderr, "\tgo doc <pkg> <sym>[.<method>]\n")
fmt.Fprintf(os.Stderr, "For more information run\n")
fmt.Fprintf(os.Stderr, "\tgo help doc\n\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
log.SetPrefix("doc: ")
flag.Usage = usage
flag.Parse()
- buildPackage, symbol := parseArg()
+ buildPackage, userPath, symbol := parseArgs()
symbol, method := parseSymbol(symbol)
- pkg := parsePackage(buildPackage)
+ pkg := parsePackage(buildPackage, userPath)
switch {
case symbol == "":
pkg.packageDoc()
}
}
-// parseArg analyzes the argument (if any) and returns the package
-// it represents and the symbol (possibly with a .method) within that
-// package. parseSymbol is used to analyze the symbol itself.
-func parseArg() (*build.Package, string) {
+// parseArgs analyzes the arguments (if any) and returns the package
+// it represents, the part of the argument the user used to identify
+// the path (or "" if it's the current package) and the symbol
+// (possibly with a .method) within that package.
+// parseSymbol is used to analyze the symbol itself.
+func parseArgs() (*build.Package, string, string) {
switch flag.NArg() {
default:
usage()
case 0:
// Easy: current directory.
- return importDir("."), ""
+ return importDir("."), "", ""
case 1:
// Done below.
+ case 2:
+ // Package must be importable.
+ pkg, err := build.Import(flag.Arg(0), "", build.ImportComment)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return pkg, flag.Arg(0), flag.Arg(1)
}
// Usual case: one argument.
arg := flag.Arg(0)
// package paths as their prefix.
pkg, err := build.Import(arg, "", build.ImportComment)
if err == nil {
- return pkg, ""
+ return pkg, arg, ""
}
// Another disambiguator: If the symbol starts with an upper
// case letter, it can only be a symbol in the current directory.
// Kills the problem caused by case-insensitive file systems
// matching an upper case name as a package name.
if isUpper(arg) {
- println("HERE", arg)
pkg, err := build.ImportDir(".", build.ImportComment)
if err == nil {
- return pkg, arg
+ return pkg, "", arg
}
}
// If it has a slash, it must be a package path but there is a symbol.
// Have we identified a package already?
pkg, err := build.Import(arg[0:period], "", build.ImportComment)
if err == nil {
- return pkg, symbol
+ return pkg, arg[0:period], symbol
}
// See if we have the basename or tail of a package, as in json for encoding/json
// or ivy/value for robpike.io/ivy/value.
path := findPackage(arg[0:period])
if path != "" {
- return importDir(path), symbol
- }
- if path != "" {
- return importDir(path), symbol
+ return importDir(path), arg[0:period], symbol
}
}
// If it has a slash, we've failed.
log.Fatalf("no such package %s", arg[0:period])
}
// Guess it's a symbol in the current directory.
- return importDir("."), arg
+ return importDir("."), "", arg
}
// importDir is just an error-catching wrapper for build.ImportDir.
// If the unexported flag (-u) is true, isExported returns true because
// it means that we treat the name as if it is exported.
func isExported(name string) bool {
- return unexported || isUpper(name)
+ return *unexported || isUpper(name)
}
// isUpper reports whether the name starts with an upper case letter.
)
type Package struct {
- name string // Package name, json for encoding/json.
- pkg *ast.Package // Parsed package.
- file *ast.File // Merged from all files in the package
- doc *doc.Package
- build *build.Package
- fs *token.FileSet // Needed for printing.
+ name string // Package name, json for encoding/json.
+ userPath string // String the user used to find this package.
+ pkg *ast.Package // Parsed package.
+ file *ast.File // Merged from all files in the package
+ doc *doc.Package
+ build *build.Package
+ fs *token.FileSet // Needed for printing.
+ buf bytes.Buffer
}
// parsePackage turns the build package we found into a parsed package
// we can then use to generate documentation.
-func parsePackage(pkg *build.Package) *Package {
+func parsePackage(pkg *build.Package, userPath string) *Package {
fs := token.NewFileSet()
// include tells parser.ParseDir which files to include.
// That means the file must be in the build package's GoFiles or CgoFiles
}
return &Package{
- name: pkg.Name,
- pkg: astPkg,
- file: ast.MergePackageFiles(astPkg, 0),
- doc: docPkg,
- build: pkg,
- fs: fs,
+ name: pkg.Name,
+ userPath: userPath,
+ pkg: astPkg,
+ file: ast.MergePackageFiles(astPkg, 0),
+ doc: docPkg,
+ build: pkg,
+ fs: fs,
}
}
-var formatBuf bytes.Buffer // One instance to minimize allocation. TODO: Buffer all output.
+func (pkg *Package) Printf(format string, args ...interface{}) {
+ fmt.Fprintf(&pkg.buf, format, args...)
+}
+
+func (pkg *Package) flush() {
+ _, err := os.Stdout.Write(pkg.buf.Bytes())
+ if err != nil {
+ log.Fatal(err)
+ }
+ pkg.buf.Reset() // Not needed, but it's a flush.
+}
+
+var newlineBytes = []byte("\n\n") // We never ask for more than 2.
+
+// newlines guarantees there are n newlines at the end of the buffer.
+func (pkg *Package) newlines(n int) {
+ for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) {
+ pkg.buf.WriteRune('\n')
+ }
+}
// emit prints the node.
func (pkg *Package) emit(comment string, node ast.Node) {
if node != nil {
- formatBuf.Reset()
- if comment != "" {
- doc.ToText(&formatBuf, comment, "", "\t", 80)
- }
- err := format.Node(&formatBuf, pkg.fs, node)
+ err := format.Node(&pkg.buf, pkg.fs, node)
if err != nil {
log.Fatal(err)
}
- if formatBuf.Len() > 0 && formatBuf.Bytes()[formatBuf.Len()-1] != '\n' {
- formatBuf.WriteRune('\n')
+ if comment != "" {
+ pkg.newlines(1)
+ doc.ToText(&pkg.buf, comment, " ", "\t", 80)
}
- os.Stdout.Write(formatBuf.Bytes())
+ pkg.newlines(1)
}
}
+var formatBuf bytes.Buffer // Reusable to avoid allocation.
+
// formatNode is a helper function for printing.
func (pkg *Package) formatNode(node ast.Node) []byte {
formatBuf.Reset()
if i < len(valueSpec.Values) && valueSpec.Values[i] != nil {
val = fmt.Sprintf(" = %s", pkg.formatNode(valueSpec.Values[i]))
}
- fmt.Printf("%s %s%s%s%s\n", decl.Tok, valueSpec.Names[0], typ, val, dotDotDot)
+ pkg.Printf("%s %s%s%s%s\n", decl.Tok, valueSpec.Names[0], typ, val, dotDotDot)
break
}
}
spec.Comment = nil
switch spec.Type.(type) {
case *ast.InterfaceType:
- fmt.Printf("type %s interface { ... }\n", spec.Name)
+ pkg.Printf("type %s interface { ... }\n", spec.Name)
case *ast.StructType:
- fmt.Printf("type %s struct { ... }\n", spec.Name)
+ pkg.Printf("type %s struct { ... }\n", spec.Name)
default:
- fmt.Printf("type %s %s\n", spec.Name, pkg.formatNode(spec.Type))
+ pkg.Printf("type %s %s\n", spec.Name, pkg.formatNode(spec.Type))
}
}
// packageDoc prints the docs for the package (package doc plus one-liners of the rest).
-// TODO: Sort the output.
func (pkg *Package) packageDoc() {
- // Package comment.
+ defer pkg.flush()
+ pkg.packageClause(false)
+
+ doc.ToText(&pkg.buf, pkg.doc.Doc, "", "\t", 80)
+ pkg.newlines(2)
+
+ pkg.valueSummary(pkg.doc.Consts)
+ pkg.valueSummary(pkg.doc.Vars)
+ pkg.funcSummary(pkg.doc.Funcs)
+ pkg.typeSummary()
+}
+
+// packageClause prints the package clause.
+// The argument boolean, if true, suppresses the output if the
+// user's argument is identical to the actual package path or
+// is empty, meaning it's the current directory.
+func (pkg *Package) packageClause(checkUserPath bool) {
+ if checkUserPath {
+ if pkg.userPath == "" || pkg.userPath == pkg.build.ImportPath {
+ return
+ }
+ }
importPath := pkg.build.ImportComment
if importPath == "" {
importPath = pkg.build.ImportPath
}
- fmt.Printf("package %s // import %q\n\n", pkg.name, importPath)
+ pkg.Printf("package %s // import %q\n\n", pkg.name, importPath)
if importPath != pkg.build.ImportPath {
- fmt.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)
+ pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)
}
- doc.ToText(os.Stdout, pkg.doc.Doc, "", "\t", 80)
- fmt.Print("\n")
-
- pkg.valueSummary(pkg.doc.Consts)
- pkg.valueSummary(pkg.doc.Vars)
- pkg.funcSummary(pkg.doc.Funcs)
- pkg.typeSummary()
}
// valueSummary prints a one-line summary for each set of values and constants.
// symbolDoc prints the docs for symbol. There may be multiple matches.
// If symbol matches a type, output includes its methods factories and associated constants.
func (pkg *Package) symbolDoc(symbol string) {
+ defer pkg.flush()
found := false
// Functions.
for _, fun := range pkg.findFuncs(symbol) {
+ if !found {
+ pkg.packageClause(true)
+ }
// Symbol is a function.
decl := fun.Decl
decl.Body = nil
values := pkg.findValues(symbol, pkg.doc.Consts)
values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...)
for _, value := range values {
+ if !found {
+ pkg.packageClause(true)
+ }
pkg.emit(value.Doc, value.Decl)
found = true
}
// Types.
for _, typ := range pkg.findTypes(symbol) {
+ if !found {
+ pkg.packageClause(true)
+ }
decl := typ.Decl
spec := pkg.findTypeSpec(decl, typ.Name)
trimUnexportedFields(spec)
// trimUnexportedFields modifies spec in place to elide unexported fields (unless
// the unexported flag is set). If spec is not a structure declartion, nothing happens.
func trimUnexportedFields(spec *ast.TypeSpec) {
- if unexported {
+ if *unexported {
// We're printing all fields.
return
}
// methodDoc prints the docs for matches of symbol.method.
func (pkg *Package) methodDoc(symbol, method string) {
+ defer pkg.flush()
types := pkg.findTypes(symbol)
if types == nil {
log.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
if !isExported(program) {
return false
}
+ if *matchCase {
+ return user == program
+ }
for _, u := range user {
p, w := utf8.DecodeRuneInString(program)
program = program[w:]
Usage:
- go doc [-u] [package|[package.]symbol[.method]]
+ go doc [-u] [-c] [package|[package.]symbol[.method]]
-Doc accepts at most one argument, indicating either a package, a symbol within a
-package, or a method of a symbol.
+Doc prints the documentation comments associated with the item identified by its
+arguments (a package, const, func, type, var, or method) followed by a one-line
+summary of each of the first-level items "under" that item (package-level declarations
+for a package, methods for a type, etc.).
+
+Doc accepts zero, one, or two arguments.
+
+Given no arguments, that is, when run as
go doc
+
+it prints the package documentation for the package in the current directory.
+
+When run with one argument, the argument is treated as a Go-syntax-like representation
+of the item to be documented. What the argument selects depends on what is installed
+in GOROOT and GOPATH, as well as the form of the argument, which is schematically
+one of these:
+
go doc <pkg>
go doc <sym>[.<method>]
go doc [<pkg>].<sym>[.<method>]
-Doc interprets the argument to see what it represents, determined by its syntax
-and which packages and symbols are present in the source directories of GOROOT and
-GOPATH.
-
-The first item in this list that succeeds is the one whose documentation is printed.
-For packages, the order of scanning is determined lexically, however the GOROOT
-tree is always scanned before GOPATH.
+The first item in this list matched by the argument is the one whose documentation
+is printed. (See the examples below.) For packages, the order of scanning is
+determined lexically, but the GOROOT tree is always scanned before GOPATH.
If there is no package specified or matched, the package in the current directory
-is selected, so "go doc" shows the documentation for the current package and
-"go doc Foo" shows the documentation for symbol Foo in the current package.
+is selected, so "go doc Foo" shows the documentation for symbol Foo in the current
+package.
-Doc prints the documentation comments associated with the top-level item the
-argument identifies (package, type, method) followed by a one-line summary of each
-of the first-level items "under" that item (package-level declarations for a
-package, methods for a type, etc.).
+The package path must be either a qualified path or a proper suffix of a path. The
+go tool's usual package mechanism does not apply: package path elements like . and
+... are not implemented by go doc.
-The package paths must be either a qualified path or a proper suffix of a path
-(see examples below). The go tool's usual package mechanism does not apply: package
-path elements like . and ... are not implemented by go doc.
+When run with two arguments, the first must be a full package path (not just a
+suffix), and the second is a symbol or symbol and method; this is similar to the
+syntax accepted by godoc:
-When matching symbols, lower-case letters match either case but upper-case letters
-match exactly. This means that there may be multiple matches in a package if
-different symbols have different cases. If this occurs, documentation for all
-matches is printed.
+ go doc <pkg> <sym>[.<method>]
+
+In all forms, when matching symbols, lower-case letters in the argument match
+either case but upper-case letters match exactly. This means that there may be
+multiple matches of a lower-case argument in a package if different symbols have
+different cases. If this occurs, documentation for all matches is printed.
Examples:
go doc
Show documentation and method summary for json.Number.
go doc json.Number.Int64 (or go doc json.number.int64)
Show documentation for json.Number's Int64 method.
+ go doc template.new
+ Show documentation for html/template's New function.
+ (html/template is lexically before text/template)
+ go doc text/template.new # One argument
+ Show documentation for text/template's New function.
+ go doc text/template new # Two arguments
+ Show documentation for text/template's New function.
Flags:
+ -c
+ Respect case when matching symbols.
-u
Show documentation for unexported as well as exported
symbols and methods.
var cmdDoc = &Command{
Run: runDoc,
- UsageLine: "doc [-u] [package|[package.]symbol[.method]]",
+ UsageLine: "doc [-u] [-c] [package|[package.]symbol[.method]]",
CustomFlags: true,
Short: "show documentation for package or symbol",
Long: `
-Doc accepts at most one argument, indicating either a package, a symbol within a
-package, or a method of a symbol.
+
+Doc prints the documentation comments associated with the item identified by its
+arguments (a package, const, func, type, var, or method) followed by a one-line
+summary of each of the first-level items "under" that item (package-level declarations
+for a package, methods for a type, etc.).
+
+Doc accepts zero, one, or two arguments.
+
+Given no arguments, that is, when run as
go doc
+
+it prints the package documentation for the package in the current directory.
+
+When run with one argument, the argument is treated as a Go-syntax-like representation
+of the item to be documented. What the argument selects depends on what is installed
+in GOROOT and GOPATH, as well as the form of the argument, which is schematically
+one of these:
+
go doc <pkg>
go doc <sym>[.<method>]
go doc [<pkg>].<sym>[.<method>]
-Doc interprets the argument to see what it represents, determined by its syntax
-and which packages and symbols are present in the source directories of GOROOT and
-GOPATH.
-
-The first item in this list that succeeds is the one whose documentation is printed.
-For packages, the order of scanning is determined lexically, however the GOROOT
-tree is always scanned before GOPATH.
+The first item in this list matched by the argument is the one whose documentation
+is printed. (See the examples below.) For packages, the order of scanning is
+determined lexically, but the GOROOT tree is always scanned before GOPATH.
If there is no package specified or matched, the package in the current directory
-is selected, so "go doc" shows the documentation for the current package and
-"go doc Foo" shows the documentation for symbol Foo in the current package.
+is selected, so "go doc Foo" shows the documentation for symbol Foo in the current
+package.
+
+The package path must be either a qualified path or a proper suffix of a path. The
+go tool's usual package mechanism does not apply: package path elements like . and
+... are not implemented by go doc.
-Doc prints the documentation comments associated with the top-level item the
-argument identifies (package, type, method) followed by a one-line summary of each
-of the first-level items "under" that item (package-level declarations for a
-package, methods for a type, etc.).
+When run with two arguments, the first must be a full package path (not just a
+suffix), and the second is a symbol or symbol and method; this is similar to the
+syntax accepted by godoc:
-The package paths must be either a qualified path or a proper suffix of a path
-(see examples below). The go tool's usual package mechanism does not apply: package
-path elements like . and ... are not implemented by go doc.
+ go doc <pkg> <sym>[.<method>]
-When matching symbols, lower-case letters match either case but upper-case letters
-match exactly. This means that there may be multiple matches in a package if
-different symbols have different cases. If this occurs, documentation for all
-matches is printed.
+In all forms, when matching symbols, lower-case letters in the argument match
+either case but upper-case letters match exactly. This means that there may be
+multiple matches of a lower-case argument in a package if different symbols have
+different cases. If this occurs, documentation for all matches is printed.
Examples:
go doc
Show documentation and method summary for json.Number.
go doc json.Number.Int64 (or go doc json.number.int64)
Show documentation for json.Number's Int64 method.
+ go doc template.new
+ Show documentation for html/template's New function.
+ (html/template is lexically before text/template)
+ go doc text/template.new # One argument
+ Show documentation for text/template's New function.
+ go doc text/template new # Two arguments
+ Show documentation for text/template's New function.
Flags:
+ -c
+ Respect case when matching symbols.
-u
Show documentation for unexported as well as exported
symbols and methods.