godoc: Fix commandline mode output.

Fix output of packages with multiple files.
Remove extra output when filter is requested.
Fixes golang/go#7115.

R=bradfitz, adg
CC=golang-codereviews
https://golang.org/cl/52750044
This commit is contained in:
Brad Garcia 2014-01-30 06:28:19 -05:00
parent bf4d636dc9
commit 2ca2bfc172
7 changed files with 91 additions and 60 deletions

View File

@ -4,11 +4,9 @@
package godoc package godoc
import ( import (
"bytes"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/printer"
"io" "io"
"log" "log"
"os" "os"
@ -106,7 +104,8 @@ func CommandLine(w io.Writer, fs vfs.NameSpace, pres *Presentation, args []strin
// If we have more than one argument, use the remaining arguments for filtering. // If we have more than one argument, use the remaining arguments for filtering.
if len(args) > 1 { if len(args) > 1 {
filterInfo(pres, args[1:], info) info.IsFiltered = true
filterInfo(args[1:], info)
} }
packageText := pres.PackageText packageText := pres.PackageText
@ -145,7 +144,9 @@ func paths(fs vfs.NameSpace, pres *Presentation, path string) (string, string) {
return pathpkg.Join(pres.PkgFSRoot(), path), path return pathpkg.Join(pres.PkgFSRoot(), path), path
} }
func filterInfo(pres *Presentation, args []string, info *PageInfo) { // filterInfo updates info to include only the nodes that match the given
// filter args.
func filterInfo(args []string, info *PageInfo) {
rx, err := makeRx(args) rx, err := makeRx(args)
if err != nil { if err != nil {
log.Fatalf("illegal regular expression from %v: %v", args, err) log.Fatalf("illegal regular expression from %v: %v", args, err)
@ -154,29 +155,21 @@ func filterInfo(pres *Presentation, args []string, info *PageInfo) {
filter := func(s string) bool { return rx.MatchString(s) } filter := func(s string) bool { return rx.MatchString(s) }
switch { switch {
case info.PAst != nil: case info.PAst != nil:
cmap := ast.NewCommentMap(info.FSet, info.PAst, info.PAst.Comments) newPAst := map[string]*ast.File{}
ast.FilterFile(info.PAst, filter) for name, a := range info.PAst {
// Special case: Don't use templates for printing cmap := ast.NewCommentMap(info.FSet, a, a.Comments)
// so we only get the filtered declarations without a.Comments = []*ast.CommentGroup{} // remove all comments.
// package clause or extra whitespace. ast.FilterFile(a, filter)
for i, d := range info.PAst.Decls { if len(a.Decls) > 0 {
// determine the comments associated with d only newPAst[name] = a
comments := cmap.Filter(d).Comments()
cn := &printer.CommentedNode{Node: d, Comments: comments}
if i > 0 {
fmt.Println()
} }
if pres.HTMLMode { for _, d := range a.Decls {
var buf bytes.Buffer // add back the comments associated with d only
pres.WriteNode(&buf, info.FSet, cn) comments := cmap.Filter(d).Comments()
FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil) a.Comments = append(a.Comments, comments...)
} else {
pres.WriteNode(os.Stdout, info.FSet, cn)
} }
fmt.Println()
} }
return info.PAst = newPAst // add only matching files.
case info.PDoc != nil: case info.PDoc != nil:
info.PDoc.Filter(filter) info.PDoc.Filter(filter)
} }

View File

@ -157,6 +157,17 @@ func TestCommandLine(t *testing.T) {
mfs := mapfs.New(map[string]string{ mfs := mapfs.New(map[string]string{
"src/pkg/bar/bar.go": `// Package bar is an example. "src/pkg/bar/bar.go": `// Package bar is an example.
package bar package bar
`,
"src/pkg/foo/foo.go": `// Package foo.
package foo
// First function is first.
func First() {
}
// Second function is second.
func Second() {
}
`, `,
"src/cmd/go/doc.go": `// The go command "src/cmd/go/doc.go": `// The go command
package main package main
@ -172,7 +183,10 @@ package main
p.cmdHandler = handlerServer{p, c, "/cmd/", "/src/cmd"} p.cmdHandler = handlerServer{p, c, "/cmd/", "/src/cmd"}
p.pkgHandler = handlerServer{p, c, "/pkg/", "/src/pkg"} p.pkgHandler = handlerServer{p, c, "/pkg/", "/src/pkg"}
p.initFuncMap() p.initFuncMap()
p.PackageText = template.Must(template.New("PackageText").Funcs(p.FuncMap()).Parse(`{{with .PAst}}{{node $ .}}{{end}}{{with .PDoc}}{{if $.IsMain}}COMMAND {{.Doc}}{{else}}PACKAGE {{.Doc}}{{end}}{{end}}`)) p.PackageText = template.Must(template.New("PackageText").Funcs(p.FuncMap()).Parse(`{{with .PAst}}{{range $filename, $ast := .}}{{$filename}}:
{{node $ $ast}}{{end}}{{end}}{{with .PDoc}}{{if $.IsMain}}COMMAND {{.Doc}}{{else}}PACKAGE {{.Doc}}{{end}}{{with .Funcs}}
{{range .}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}}{{end}}{{end}}{{end}}`))
for _, tc := range []struct { for _, tc := range []struct {
desc string desc string
@ -190,10 +204,20 @@ package main
args: []string{"bar"}, args: []string{"bar"},
exp: "PACKAGE Package bar is an example.\n", exp: "PACKAGE Package bar is an example.\n",
}, },
{
desc: "package w. filter",
args: []string{"foo", "First"},
exp: "PACKAGE \nfunc First()\n First function is first.\n",
},
{ {
desc: "source mode", desc: "source mode",
args: []string{"src/bar"}, args: []string{"src/bar"},
exp: "// Package bar is an example.\npackage bar\n", exp: "bar/bar.go:\n// Package bar is an example.\npackage bar\n",
},
{
desc: "source mode w. filter",
args: []string{"src/foo", "Second"},
exp: "// Second function is second.\nfunc Second() {\n}",
}, },
{ {
desc: "command", desc: "command",

View File

@ -219,12 +219,13 @@ type PageInfo struct {
Err error // error or nil Err error // error or nil
// package info // package info
FSet *token.FileSet // nil if no package documentation FSet *token.FileSet // nil if no package documentation
PDoc *doc.Package // nil if no package documentation PDoc *doc.Package // nil if no package documentation
Examples []*doc.Example // nil if no example code Examples []*doc.Example // nil if no example code
Notes map[string][]*doc.Note // nil if no package Notes Notes map[string][]*doc.Note // nil if no package Notes
PAst *ast.File // nil if no AST with package exports PAst map[string]*ast.File // nil if no AST with package exports
IsMain bool // true for package main IsMain bool // true for package main
IsFiltered bool // true if results were filtered
// directory info // directory info
Dirs *DirList // nil if no directory information Dirs *DirList // nil if no directory information

View File

@ -24,7 +24,7 @@ func (c *Corpus) parseFile(fset *token.FileSet, filename string, mode parser.Mod
return parser.ParseFile(fset, filename, src, mode) return parser.ParseFile(fset, filename, src, mode)
} }
func (c *Corpus) parseFiles(fset *token.FileSet, abspath string, localnames []string) (map[string]*ast.File, error) { func (c *Corpus) parseFiles(fset *token.FileSet, relpath string, abspath string, localnames []string) (map[string]*ast.File, error) {
files := make(map[string]*ast.File) files := make(map[string]*ast.File)
for _, f := range localnames { for _, f := range localnames {
absname := pathpkg.Join(abspath, f) absname := pathpkg.Join(abspath, f)
@ -32,7 +32,7 @@ func (c *Corpus) parseFiles(fset *token.FileSet, abspath string, localnames []st
if err != nil { if err != nil {
return nil, err return nil, err
} }
files[absname] = file files[pathpkg.Join(relpath, f)] = file
} }
return files, nil return files, nil

View File

@ -97,7 +97,7 @@ func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode)
if len(pkgfiles) > 0 { if len(pkgfiles) > 0 {
// build package AST // build package AST
fset := token.NewFileSet() fset := token.NewFileSet()
files, err := h.c.parseFiles(fset, abspath, pkgfiles) files, err := h.c.parseFiles(fset, relpath, abspath, pkgfiles)
if err != nil { if err != nil {
info.Err = err info.Err = err
return info return info
@ -128,7 +128,7 @@ func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode)
// collect examples // collect examples
testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...) testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
files, err = h.c.parseFiles(fset, abspath, testfiles) files, err = h.c.parseFiles(fset, relpath, abspath, testfiles)
if err != nil { if err != nil {
log.Println("parsing examples:", err) log.Println("parsing examples:", err)
} }
@ -156,7 +156,7 @@ func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode)
if mode&NoFiltering == 0 { if mode&NoFiltering == 0 {
packageExports(fset, pkg) packageExports(fset, pkg)
} }
info.PAst = ast.MergePackageFiles(pkg, 0) info.PAst = files
} }
info.IsMain = pkgname == "main" info.IsMain = pkgname == "main"
} }
@ -218,7 +218,10 @@ func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var tabtitle, title, subtitle string var tabtitle, title, subtitle string
switch { switch {
case info.PAst != nil: case info.PAst != nil:
tabtitle = info.PAst.Name.Name for _, ast := range info.PAst {
tabtitle = ast.Name.Name
break
}
case info.PDoc != nil: case info.PDoc != nil:
tabtitle = info.PDoc.Name tabtitle = info.PDoc.Name
default: default:

View File

@ -1,51 +1,56 @@
{{with .PAst}}{{node $ .}}{{end}}{{/* {{$info := .}}{{$filtered := .IsFiltered}}{{/*
---------------------------------------
*/}}{{if $filtered}}{{range .PAst}}{{range .Decls}}{{node $info .}}{{end}}{{end}}{{else}}{{with .PAst}}{{range $filename, $ast := .}}{{$filename}}:
{{node $ $ast}}{{end}}{{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .PDoc}}{{if $.IsMain}}COMMAND DOCUMENTATION */}}{{with .PDoc}}{{if $.IsMain}}COMMAND DOCUMENTATION
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{else}}PACKAGE DOCUMENTATION {{else}}{{if not $filtered}}PACKAGE DOCUMENTATION
package {{.Name}} package {{.Name}}
import "{{.ImportPath}}" import "{{.ImportPath}}"
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{example_text $ "" " "}}{{/* {{example_text $ "" " "}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Consts}} */}}{{with .Consts}}{{if not $filtered}}
CONSTANTS CONSTANTS
{{range .}}{{node $ .Decl}} {{end}}{{range .}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{end}}{{end}}{{/* {{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Vars}} */}}{{with .Vars}}{{if not $filtered}}
VARIABLES VARIABLES
{{range .}}{{node $ .Decl}} {{end}}{{range .}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{end}}{{end}}{{/* {{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Funcs}} */}}{{with .Funcs}}{{if not $filtered}}
FUNCTIONS FUNCTIONS
{{range .}}{{node $ .Decl}} {{end}}{{range .}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{example_text $ .Name " "}}{{end}}{{end}}{{/* {{example_text $ .Name " "}}{{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Types}} */}}{{with .Types}}{{if not $filtered}}
TYPES TYPES
{{range .}}{{$tname := .Name}}{{node $ .Decl}} {{end}}{{range .}}{{$tname := .Name}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{range .Consts}}{{node $ .Decl}} {{range .Consts}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}

View File

@ -751,54 +751,59 @@ $(document).ready(function() {
{{end}} {{end}}
{{end}} {{end}}
`, `,
"package.txt": `{{with .PAst}}{{node $ .}}{{end}}{{/* "package.txt": `{{$info := .}}{{$filtered := .IsFiltered}}{{/*
---------------------------------------
*/}}{{if $filtered}}{{range .PAst}}{{range .Decls}}{{node $info .}}{{end}}{{end}}{{else}}{{with .PAst}}{{range $filename, $ast := .}}{{$filename}}:
{{node $ $ast}}{{end}}{{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .PDoc}}{{if $.IsMain}}COMMAND DOCUMENTATION */}}{{with .PDoc}}{{if $.IsMain}}COMMAND DOCUMENTATION
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{else}}PACKAGE DOCUMENTATION {{else}}{{if not $filtered}}PACKAGE DOCUMENTATION
package {{.Name}} package {{.Name}}
import "{{.ImportPath}}" import "{{.ImportPath}}"
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{example_text $ "" " "}}{{/* {{example_text $ "" " "}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Consts}} */}}{{with .Consts}}{{if not $filtered}}
CONSTANTS CONSTANTS
{{range .}}{{node $ .Decl}} {{end}}{{range .}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{end}}{{end}}{{/* {{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Vars}} */}}{{with .Vars}}{{if not $filtered}}
VARIABLES VARIABLES
{{range .}}{{node $ .Decl}} {{end}}{{range .}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{end}}{{end}}{{/* {{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Funcs}} */}}{{with .Funcs}}{{if not $filtered}}
FUNCTIONS FUNCTIONS
{{range .}}{{node $ .Decl}} {{end}}{{range .}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{example_text $ .Name " "}}{{end}}{{end}}{{/* {{example_text $ .Name " "}}{{end}}{{end}}{{/*
--------------------------------------- ---------------------------------------
*/}}{{with .Types}} */}}{{with .Types}}{{if not $filtered}}
TYPES TYPES
{{range .}}{{$tname := .Name}}{{node $ .Decl}} {{end}}{{range .}}{{$tname := .Name}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}
{{range .Consts}}{{node $ .Decl}} {{range .Consts}}{{node $ .Decl}}
{{comment_text .Doc " " "\t"}} {{comment_text .Doc " " "\t"}}