]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: better UI for go doc
authorRob Pike <r@golang.org>
Wed, 29 Apr 2015 03:55:01 +0000 (20:55 -0700)
committerRob Pike <r@golang.org>
Wed, 29 Apr 2015 19:12:53 +0000 (19:12 +0000)
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>
src/cmd/doc/main.go
src/cmd/doc/pkg.go
src/cmd/go/alldocs.go
src/cmd/go/doc.go

index e0178effcef1aeb089a72ae35adbdef45317242f..22694287e9a4f1169efb4e1681063b4090244c21 100644 (file)
@@ -2,14 +2,26 @@
 // 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
@@ -28,14 +40,10 @@ import (
 )
 
 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")
@@ -43,6 +51,7 @@ func usage() {
        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")
@@ -55,9 +64,9 @@ func main() {
        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()
@@ -69,18 +78,27 @@ func main() {
        }
 }
 
-// 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)
@@ -90,17 +108,16 @@ func parseArg() (*build.Package, string) {
        // 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.
@@ -125,16 +142,13 @@ func parseArg() (*build.Package, string) {
                // 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.
@@ -142,7 +156,7 @@ func parseArg() (*build.Package, string) {
                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.
@@ -194,7 +208,7 @@ func isIdentifier(name string) {
 // 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.
index e08b756239c2d707ef2822fb0332ea2d7d2336b8..580a91f7e6946101d2ba4eaa1227f09cec1be54c 100644 (file)
@@ -20,17 +20,19 @@ import (
 )
 
 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
@@ -73,35 +75,54 @@ func parsePackage(pkg *build.Package) *Package {
        }
 
        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()
@@ -137,7 +158,7 @@ func (pkg *Package) oneLineValueGenDecl(decl *ast.GenDecl) {
                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
        }
 }
@@ -148,33 +169,46 @@ func (pkg *Package) oneLineTypeDecl(spec *ast.TypeSpec) {
        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.
@@ -265,9 +299,13 @@ 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.
 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
@@ -278,11 +316,17 @@ func (pkg *Package) symbolDoc(symbol string) {
        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)
@@ -306,7 +350,7 @@ func (pkg *Package) symbolDoc(symbol string) {
 // 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
        }
@@ -349,6 +393,7 @@ func trimUnexportedFields(spec *ast.TypeSpec) {
 
 // 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)
@@ -376,6 +421,9 @@ func match(user, program string) bool {
        if !isExported(program) {
                return false
        }
+       if *matchCase {
+               return user == program
+       }
        for _, u := range user {
                p, w := utf8.DecodeRuneInString(program)
                program = program[w:]
index 59d7962a458b5b7d018472890139e29800e3e861..39233b855b997b1648af6d065524e071f47148fb 100644 (file)
@@ -188,41 +188,52 @@ Show documentation for package or symbol
 
 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
@@ -238,8 +249,17 @@ Examples:
                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.
index 98ce34077b6a1cacaeafd7c3bce97e8b88ba18a2..a9bda4dceb6367f17f9a8abddc89d8f289bcb5f2 100644 (file)
@@ -6,43 +6,55 @@ package main
 
 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
@@ -58,8 +70,17 @@ Examples:
                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.