internal/godoc: simplify, isolate pkg docs tree code
- IsGOROOT is always true (the only docs we serve)
- SummarizePackage is unused
- newDirectory is only called on the root with unlimited depth
- Name is redundant with Path
- Directory.Depth is unused
- only DirEntry is assigned to dirEntryOrFileInfo
- parallel walk is only needed for huge trees (GOROOT is not)
- separate entirely from Corpus
- pull new DocTree out of handlerServer
This will let us better separate the package doc tree from HTTP service
and from the general document server.
Change-Id: Iac667a0ff624306cdd535a33dd22dcb04062b46c
Reviewed-on: https://go-review.googlesource.com/c/website/+/296378
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/_content/lib/godoc/packageroot.html b/_content/lib/godoc/packageroot.html
index 8655dcd..2b5c5e6 100644
--- a/_content/lib/godoc/packageroot.html
+++ b/_content/lib/godoc/packageroot.html
@@ -45,7 +45,6 @@
{{range .List}}
<tr>
- {{if .IsGOROOT}}
{{if $.DirFlat}}
{{if .HasPkg}}
<td class="pkg-name">
@@ -57,10 +56,9 @@
<a href="{{html .Path}}/{{modeQueryString $.Mode | html}}">{{html .Name}}</a>
</td>
{{end}}
- <td class="pkg-synopsis">
- {{html .Synopsis}}
- </td>
- {{end}}
+ <td class="pkg-synopsis">
+ {{html .Synopsis}}
+ </td>
</tr>
{{end}}
</table>
diff --git a/internal/godoc/corpus.go b/internal/godoc/corpus.go
index 6de01d1..7c403f6 100644
--- a/internal/godoc/corpus.go
+++ b/internal/godoc/corpus.go
@@ -8,7 +8,6 @@
package godoc
import (
- "errors"
"io/fs"
"sync"
"time"
@@ -27,25 +26,12 @@
// Verbose logging.
Verbose bool
- // SummarizePackage optionally specifies a function to
- // summarize a package. It exists as an optimization to
- // avoid reading files to parse package comments.
- //
- // If SummarizePackage returns false for ok, the caller
- // ignores all return values and parses the files in the package
- // as if SummarizePackage were nil.
- //
- // If showList is false, the package is hidden from the
- // package listing.
- SummarizePackage func(pkg string) (summary string, showList, ok bool)
-
// Send a value on this channel to trigger a metadata refresh.
// It is buffered so that if a signal is not lost if sent
// during a refresh.
refreshMetadataSignal chan bool
// file system information
- fsTree rwValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
fsModified rwValue // timestamp of last call to invalidateIndex
docMetadata rwValue // mapping from paths to *Metadata
@@ -77,9 +63,6 @@
// Init initializes Corpus, once options on Corpus are set.
// It must be called before any subsequent method calls.
func (c *Corpus) Init() error {
- if err := c.initFSTree(); err != nil {
- return err
- }
c.updateMetadata()
go c.refreshMetadataLoop()
@@ -88,12 +71,3 @@
c.initMu.Unlock()
return nil
}
-
-func (c *Corpus) initFSTree() error {
- dir := c.newDirectory("/", -1)
- if dir == nil {
- return errors.New("godoc: corpus fstree is nil")
- }
- c.fsTree.Set(dir)
- return nil
-}
diff --git a/internal/godoc/dirtrees.go b/internal/godoc/dirtrees.go
index f7adf99..ddc2ae8 100644
--- a/internal/godoc/dirtrees.go
+++ b/internal/godoc/dirtrees.go
@@ -16,104 +16,45 @@
"io/fs"
"log"
pathpkg "path"
- "runtime"
"sort"
"strings"
)
-// Conventional name for directories containing test data.
-// Excluded from directory trees.
-//
-const testdataDirName = "testdata"
-
type Directory struct {
- Depth int
- Path string // directory path; includes Name
- Name string // directory name
+ Path string // directory path
HasPkg bool // true if the directory contains at least one package
Synopsis string // package documentation, if any
- IsGOROOT bool // is this GOROOT
Dirs []*Directory // subdirectories
}
-type dirEntryOrFileInfo interface {
- Name() string
- IsDir() bool
+func (d *Directory) Name() string {
+ return pathpkg.Base(d.Path)
}
-func isGoFile(fi dirEntryOrFileInfo) bool {
+func isPkgFile(fi fs.DirEntry) bool {
name := fi.Name()
return !fi.IsDir() &&
- len(name) > 0 && name[0] != '.' && // ignore .files
- pathpkg.Ext(name) == ".go"
-}
-
-func isPkgFile(fi dirEntryOrFileInfo) bool {
- return isGoFile(fi) &&
+ pathpkg.Ext(name) == ".go" &&
!strings.HasSuffix(fi.Name(), "_test.go") // ignore test files
}
-func isPkgDir(fi dirEntryOrFileInfo) bool {
+func isPkgDir(fi fs.DirEntry) bool {
name := fi.Name()
- return fi.IsDir() && len(name) > 0 &&
- name[0] != '_' && name[0] != '.' // ignore _files and .files
+ return fi.IsDir() &&
+ name != "testdata" &&
+ len(name) > 0 && name[0] != '_' && name[0] != '.' // ignore _files and .files
}
-type treeBuilder struct {
- c *Corpus
- maxDepth int
-}
-
-// ioGate is a semaphore controlling VFS activity (ReadDir, parseFile, etc).
-// Send before an operation and receive after.
-var ioGate = make(chan struct{}, 20)
-
-// workGate controls the number of concurrent workers. Too many concurrent
-// workers and performance degrades and the race detector gets overwhelmed. If
-// we cannot check out a concurrent worker, work is performed by the main thread
-// instead of spinning up another goroutine.
-var workGate = make(chan struct{}, runtime.NumCPU()*4)
-
-func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory {
- if name == testdataDirName {
- return nil
- }
-
- if depth >= b.maxDepth {
- // return a dummy directory so that the parent directory
- // doesn't get discarded just because we reached the max
- // directory depth
- return &Directory{
- Depth: depth,
- Path: path,
- Name: name,
- }
- }
-
+func newDirTree(fsys fs.FS, fset *token.FileSet, path string) *Directory {
var synopses [3]string // prioritized package documentation (0 == highest priority)
- show := true // show in package listing
hasPkgFiles := false
haveSummary := false
- if hook := b.c.SummarizePackage; hook != nil {
- if summary, show0, ok := hook(strings.TrimPrefix(path, "/src/")); ok {
- hasPkgFiles = true
- show = show0
- synopses[0] = summary
- haveSummary = true
- }
- }
-
- ioGate <- struct{}{}
- list, err := fs.ReadDir(b.c.fs, toFS(path))
- <-ioGate
+ list, err := fs.ReadDir(fsys, toFS(path))
if err != nil {
// TODO: propagate more. See golang.org/issue/14252.
- // For now:
- if b.c.Verbose {
- log.Printf("newDirTree reading %s: %v", path, err)
- }
+ log.Printf("newDirTree reading %s: %v", path, err)
}
// determine number of subdirectories and if there are package files
@@ -121,38 +62,24 @@
var dirs []*Directory
for _, d := range list {
- filename := pathpkg.Join(path, d.Name())
+ name := d.Name()
+ filename := pathpkg.Join(path, name)
switch {
case isPkgDir(d):
- name := d.Name()
- select {
- case workGate <- struct{}{}:
- ch := make(chan *Directory, 1)
- dirchs = append(dirchs, ch)
- go func() {
- ch <- b.newDirTree(fset, filename, name, depth+1)
- <-workGate
- }()
- default:
- // no free workers, do work synchronously
- dir := b.newDirTree(fset, filename, name, depth+1)
- if dir != nil {
- dirs = append(dirs, dir)
- }
+ dir := newDirTree(fsys, fset, filename)
+ if dir != nil {
+ dirs = append(dirs, dir)
}
+
case !haveSummary && isPkgFile(d):
// looks like a package file, but may just be a file ending in ".go";
// don't just count it yet (otherwise we may end up with hasPkgFiles even
// though the directory doesn't contain any real package files - was bug)
// no "optimal" package synopsis yet; continue to collect synopses
- ioGate <- struct{}{}
const flags = parser.ParseComments | parser.PackageClauseOnly
- file, err := b.c.parseFile(fset, filename, flags)
- <-ioGate
+ file, err := parseFile(fsys, fset, filename, flags)
if err != nil {
- if b.c.Verbose {
- log.Printf("Error parsing %v: %v", filename, err)
- }
+ log.Printf("parsing %v: %v", filename, err)
break
}
@@ -186,7 +113,7 @@
// We need to sort the dirs slice because
// it is appended again after reading from dirchs.
sort.Slice(dirs, func(i, j int) bool {
- return dirs[i].Name < dirs[j].Name
+ return dirs[i].Path < dirs[j].Path
})
// if there are no package files and no subdirectories
@@ -204,21 +131,13 @@
}
return &Directory{
- Depth: depth,
Path: path,
- Name: name,
- HasPkg: hasPkgFiles && show, // TODO(bradfitz): add proper Hide field?
+ HasPkg: hasPkgFiles,
Synopsis: synopsis,
- IsGOROOT: isGOROOT(b.c.fs),
Dirs: dirs,
}
}
-func isGOROOT(fsys fs.FS) bool {
- _, err := fs.Stat(fsys, "src/math/abs.go")
- return err == nil
-}
-
// toFS returns the io/fs name for path (no leading slash).
func toFS(path string) string {
if path == "/" {
@@ -227,112 +146,68 @@
return pathpkg.Clean(strings.TrimPrefix(path, "/"))
}
-// newDirectory creates a new package directory tree with at most maxDepth
-// levels, anchored at root. The result tree is pruned such that it only
-// contains directories that contain package files or that contain
-// subdirectories containing package files (transitively). If a non-nil
-// pathFilter is provided, directory paths additionally must be accepted
-// by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is
-// provided for maxDepth, nodes at larger depths are pruned as well; they
-// are assumed to contain package files even if their contents are not known
-// (i.e., in this case the tree may contain directories w/o any package files).
-//
-func (c *Corpus) newDirectory(root string, maxDepth int) *Directory {
- // The root could be a symbolic link so use Stat not Lstat.
- d, err := fs.Stat(c.fs, toFS(root))
- // If we fail here, report detailed error messages; otherwise
- // is is hard to see why a directory tree was not built.
- switch {
- case err != nil:
- log.Printf("newDirectory(%s): %s", root, err)
- return nil
- case root != "/" && !isPkgDir(d):
- log.Printf("newDirectory(%s): not a package directory", root)
- return nil
- case root == "/" && !d.IsDir():
- log.Printf("newDirectory(%s): not a directory", root)
- return nil
- }
- if maxDepth < 0 {
- maxDepth = 1e6 // "infinity"
- }
- b := treeBuilder{c, maxDepth}
- // the file set provided is only for local parsing, no position
- // information escapes and thus we don't need to save the set
- return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
+// walk calls f(d, depth) for each directory d in the tree rooted at dir, including dir itself.
+// The depth argument specifies the depth of d in the tree.
+// The depth of dir itself is 0.
+func (dir *Directory) walk(f func(d *Directory, depth int)) {
+ walkDirs(f, dir, 0)
}
-func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) {
- if dir != nil {
- if !skipRoot {
- c <- dir
- }
- for _, d := range dir.Dirs {
- d.walk(c, false)
- }
+func walkDirs(f func(d *Directory, depth int), d *Directory, depth int) {
+ f(d, depth)
+ for _, sub := range d.Dirs {
+ walkDirs(f, sub, depth+1)
}
}
-func (dir *Directory) iter(skipRoot bool) <-chan *Directory {
- c := make(chan *Directory)
- go func() {
- dir.walk(c, skipRoot)
- close(c)
- }()
- return c
-}
-
-func (dir *Directory) lookupLocal(name string) *Directory {
- for _, d := range dir.Dirs {
- if d.Name == name {
- return d
- }
- }
- return nil
-}
-
-func splitPath(p string) []string {
- p = strings.TrimPrefix(p, "/")
- if p == "" {
- return nil
- }
- return strings.Split(p, "/")
-}
-
// lookup looks for the *Directory for a given path, relative to dir.
func (dir *Directory) lookup(path string) *Directory {
- d := splitPath(dir.Path)
- p := splitPath(path)
- i := 0
- for i < len(d) {
- if i >= len(p) || d[i] != p[i] {
+ path = pathpkg.Join(dir.Path, path)
+ if path == dir.Path {
+ return dir
+ }
+ dirPathLen := len(dir.Path)
+ if dir.Path == "/" {
+ dirPathLen = 0 // so path[dirPathLen] is a slash
+ }
+ if !strings.HasPrefix(path, dir.Path) || path[dirPathLen] != '/' {
+ println("NO", path, dir.Path)
+ return nil
+ }
+ d := dir
+Walk:
+ for i := dirPathLen + 1; i <= len(path); i++ {
+ if i == len(path) || path[i] == '/' {
+ // Find next child along path.
+ for _, sub := range d.Dirs {
+ if sub.Path == path[:i] {
+ d = sub
+ continue Walk
+ }
+ }
+ println("LOST", path[:i])
return nil
}
- i++
}
- for dir != nil && i < len(p) {
- dir = dir.lookupLocal(p[i])
- i++
- }
- return dir
+ return d
}
-// DirEntry describes a directory entry. The Depth and Height values
-// are useful for presenting an entry in an indented fashion.
-//
+// DirEntry describes a directory entry.
+// The Depth gives the directory depth relative to the overall list,
+// for use in presenting a hierarchical directory entry.
type DirEntry struct {
Depth int // >= 0
- Height int // = DirList.MaxHeight - Depth, > 0
- Path string // directory path; includes Name, relative to DirList root
- Name string // directory name
+ Path string // relative path to directory from listing start
HasPkg bool // true if the directory contains at least one package
Synopsis string // package documentation, if any
- IsGOROOT bool // root type of the filesystem containing the direntry
+}
+
+func (d *DirEntry) Name() string {
+ return pathpkg.Base(d.Path)
}
type DirList struct {
- MaxHeight int // directory tree height, > 0
- List []DirEntry
+ List []DirEntry
}
// listing creates a (linear) directory listing from a directory tree.
@@ -340,52 +215,32 @@
// If filter is set, only the directory entries whose paths match the filter
// are included.
//
-func (dir *Directory) listing(skipRoot bool, filter func(string) bool) *DirList {
+func (dir *Directory) listing(filter func(string) bool) *DirList {
if dir == nil {
return nil
}
- // determine number of entries n and maximum height
- n := 0
- minDepth := 1 << 30 // infinity
- maxDepth := 0
- for d := range dir.iter(skipRoot) {
- n++
- if minDepth > d.Depth {
- minDepth = d.Depth
+ var list []DirEntry
+ dir.walk(func(d *Directory, depth int) {
+ if depth == 0 || filter != nil && !filter(d.Path) {
+ return
}
- if maxDepth < d.Depth {
- maxDepth = d.Depth
- }
- }
- maxHeight := maxDepth - minDepth + 1
-
- if n == 0 {
- return nil
- }
-
- // create list
- list := make([]DirEntry, 0, n)
- for d := range dir.iter(skipRoot) {
- if filter != nil && !filter(d.Path) {
- continue
- }
- var p DirEntry
- p.Depth = d.Depth - minDepth
- p.Height = maxHeight - p.Depth
// the path is relative to root.Path - remove the root.Path
// prefix (the prefix should always be present but avoid
// crashes and check)
path := strings.TrimPrefix(d.Path, dir.Path)
// remove leading separator if any - path must be relative
path = strings.TrimPrefix(path, "/")
- p.Path = path
- p.Name = d.Name
- p.HasPkg = d.HasPkg
- p.Synopsis = d.Synopsis
- p.IsGOROOT = d.IsGOROOT
- list = append(list, p)
- }
+ list = append(list, DirEntry{
+ Depth: depth,
+ Path: path,
+ HasPkg: d.HasPkg,
+ Synopsis: d.Synopsis,
+ })
+ })
- return &DirList{maxHeight, list}
+ if len(list) == 0 {
+ return nil
+ }
+ return &DirList{list}
}
diff --git a/internal/godoc/dirtrees_test.go b/internal/godoc/dirtrees_test.go
index 766bc55..e228901 100644
--- a/internal/godoc/dirtrees_test.go
+++ b/internal/godoc/dirtrees_test.go
@@ -8,6 +8,7 @@
package godoc
import (
+ "go/token"
"os"
"runtime"
"sort"
@@ -15,17 +16,14 @@
)
func TestNewDirTree(t *testing.T) {
- c := NewCorpus(os.DirFS(runtime.GOROOT()))
- // 3 levels deep is enough for testing
- dir := c.newDirectory("/", 3)
-
+ dir := newDirTree(os.DirFS(runtime.GOROOT()), token.NewFileSet(), "/src")
processDir(t, dir)
}
func processDir(t *testing.T, dir *Directory) {
var list []string
for _, d := range dir.Dirs {
- list = append(list, d.Name)
+ list = append(list, d.Name())
// recursively process the lower level
processDir(t, d)
}
@@ -45,24 +43,6 @@
b.ResetTimer()
b.ReportAllocs()
for tries := 0; tries < b.N; tries++ {
- corpus := NewCorpus(fs)
- corpus.newDirectory("/", -1)
- }
-}
-
-func TestIsGOROOT(t *testing.T) {
- tests := []struct {
- path string
- isGOROOT bool
- }{
- {runtime.GOROOT(), true},
- {"/tmp/", false},
- }
-
- for _, item := range tests {
- fs := os.DirFS(item.path)
- if isGOROOT(fs) != item.isGOROOT {
- t.Errorf("%s: isGOROOT = %v, want %v", item.path, !item.isGOROOT, item.isGOROOT)
- }
+ newDirTree(fs, token.NewFileSet(), "/src")
}
}
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
index 25c3cd7..6031666 100644
--- a/internal/godoc/godoc.go
+++ b/internal/godoc/godoc.go
@@ -17,15 +17,8 @@
"strconv"
"strings"
"text/template"
- "time"
)
-// Fake relative package path for built-ins. Documentation for all globals
-// (not just exported ones) will be shown for packages in this directory,
-// and there will be no association of consts, vars, and factory functions
-// with types (see issue 6645).
-const builtinPkgPath = "builtin"
-
// FuncMap defines template functions used in godoc templates.
//
// Convention: template function names ending in "_html" or "_url" produce
@@ -105,9 +98,8 @@
IsFiltered bool // true if results were filtered
// directory info
- Dirs *DirList // nil if no directory information
- DirTime time.Time // directory time stamp
- DirFlat bool // if set, show directory in a flat (non-indented) manner
+ Dirs *DirList // nil if no directory information
+ DirFlat bool // if set, show directory in a flat (non-indented) manner
}
func (info *PageInfo) IsEmpty() bool {
diff --git a/internal/godoc/parser.go b/internal/godoc/parser.go
index f742596..ec03639 100644
--- a/internal/godoc/parser.go
+++ b/internal/godoc/parser.go
@@ -48,8 +48,8 @@
}
}
-func (c *Corpus) parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
- src, err := fs.ReadFile(c.fs, toFS(filename))
+func parseFile(fsys fs.FS, fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
+ src, err := fs.ReadFile(fsys, toFS(filename))
if err != nil {
return nil, err
}
@@ -61,11 +61,11 @@
return parser.ParseFile(fset, filename, src, mode)
}
-func (c *Corpus) parseFiles(fset *token.FileSet, relpath string, abspath string, localnames []string) (map[string]*ast.File, error) {
+func parseFiles(fsys fs.FS, fset *token.FileSet, relpath string, abspath string, localnames []string) (map[string]*ast.File, error) {
files := make(map[string]*ast.File)
for _, f := range localnames {
absname := pathpkg.Join(abspath, f)
- file, err := c.parseFile(fset, absname, parser.ParseComments)
+ file, err := parseFile(fsys, fset, absname, parser.ParseComments)
if err != nil {
return nil, err
}
diff --git a/internal/godoc/pres.go b/internal/godoc/pres.go
index 688400b..2689c4a 100644
--- a/internal/godoc/pres.go
+++ b/internal/godoc/pres.go
@@ -19,8 +19,6 @@
mux *http.ServeMux
fileServer http.Handler
- cmdHandler handlerServer
- pkgHandler handlerServer
DirlistHTML,
ErrorHTML,
@@ -52,22 +50,12 @@
mux: http.NewServeMux(),
fileServer: http.FileServer(http.FS(c.fs)),
}
- p.cmdHandler = handlerServer{
- p: p,
- c: c,
- pattern: "/cmd/",
- fsRoot: "/src",
+ docs := &docServer{
+ p: p,
+ d: NewDocTree(c.fs),
}
- p.pkgHandler = handlerServer{
- p: p,
- c: c,
- pattern: "/pkg/",
- stripPrefix: "pkg/",
- fsRoot: "/src",
- exclude: []string{"/src/cmd"},
- }
- p.cmdHandler.registerWithMux(p.mux)
- p.pkgHandler.registerWithMux(p.mux)
+ p.mux.Handle("/cmd/", docs)
+ p.mux.Handle("/pkg/", docs)
p.mux.HandleFunc("/", p.ServeFile)
return p
}
@@ -80,26 +68,6 @@
p.mux.ServeHTTP(w, r)
}
-func (p *Presentation) PkgFSRoot() string {
- return p.pkgHandler.fsRoot
-}
-
-func (p *Presentation) CmdFSRoot() string {
- return p.cmdHandler.fsRoot
-}
-
-// TODO(bradfitz): move this to be a method on Corpus. Just moving code around for now,
-// but this doesn't feel right.
-func (p *Presentation) GetPkgPageInfo(abspath, relpath string, mode PageInfoMode) *PageInfo {
- return p.pkgHandler.GetPageInfo(abspath, relpath, mode, "", "")
-}
-
-// TODO(bradfitz): move this to be a method on Corpus. Just moving code around for now,
-// but this doesn't feel right.
-func (p *Presentation) GetCmdPageInfo(abspath, relpath string, mode PageInfoMode) *PageInfo {
- return p.cmdHandler.GetPageInfo(abspath, relpath, mode, "", "")
-}
-
func (p *Presentation) googleCN(r *http.Request) bool {
return p.GoogleCN != nil && p.GoogleCN(r)
}
diff --git a/internal/godoc/server.go b/internal/godoc/server.go
index 6640d97..a16a95c 100644
--- a/internal/godoc/server.go
+++ b/internal/godoc/server.go
@@ -10,7 +10,6 @@
import (
"bytes"
"encoding/json"
- "errors"
"fmt"
"go/ast"
"go/build"
@@ -35,19 +34,27 @@
"golang.org/x/website/internal/texthtml"
)
-// handlerServer is a migration from an old godoc http Handler type.
-// This should probably merge into something else.
-type handlerServer struct {
- p *Presentation
- c *Corpus // copy of p.Corpus
- pattern string // url pattern; e.g. "/pkg/"
- stripPrefix string // prefix to strip from import path; e.g. "pkg/"
- fsRoot string // file system root to which the pattern is mapped; e.g. "/src"
- exclude []string // file system paths to exclude; e.g. "/src/cmd"
+type DocTree struct {
+ fs fs.FS
+ root *Directory
}
-func (s *handlerServer) registerWithMux(mux *http.ServeMux) {
- mux.Handle(s.pattern, s)
+func NewDocTree(fsys fs.FS) *DocTree {
+ src := newDirTree(fsys, token.NewFileSet(), "/src")
+ root := &Directory{
+ Path: "/",
+ Dirs: []*Directory{src},
+ }
+ return &DocTree{
+ fs: fsys,
+ root: root,
+ }
+}
+
+// docServer serves a package doc tree (/cmd or /pkg).
+type docServer struct {
+ p *Presentation
+ d *DocTree
}
// GetPageInfo returns the PageInfo for a package directory abspath. If the
@@ -57,8 +64,7 @@
// directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub-
// directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is
// set to the respective error but the error is not logged.
-//
-func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode, goos, goarch string) *PageInfo {
+func (d *DocTree) GetPageInfo(abspath, relpath string, mode PageInfoMode, goos, goarch string) *PageInfo {
info := &PageInfo{Dirname: abspath, Mode: mode}
// Restrict to the package files that would be used when building
@@ -70,11 +76,11 @@
ctxt := build.Default
ctxt.IsAbsPath = pathpkg.IsAbs
ctxt.IsDir = func(path string) bool {
- fi, err := fs.Stat(h.c.fs, toFS(filepath.ToSlash(path)))
+ fi, err := fs.Stat(d.fs, toFS(filepath.ToSlash(path)))
return err == nil && fi.IsDir()
}
ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
- f, err := fs.ReadDir(h.c.fs, toFS(filepath.ToSlash(dir)))
+ f, err := fs.ReadDir(d.fs, toFS(filepath.ToSlash(dir)))
filtered := make([]os.FileInfo, 0, len(f))
for _, i := range f {
if mode&NoFiltering != 0 || i.Name() != "internal" {
@@ -87,7 +93,7 @@
return filtered, err
}
ctxt.OpenFile = func(name string) (r io.ReadCloser, err error) {
- data, err := fs.ReadFile(h.c.fs, toFS(filepath.ToSlash(name)))
+ data, err := fs.ReadFile(d.fs, toFS(filepath.ToSlash(name)))
if err != nil {
return nil, err
}
@@ -133,7 +139,7 @@
if len(pkgfiles) > 0 {
// build package AST
fset := token.NewFileSet()
- files, err := h.c.parseFiles(fset, relpath, abspath, pkgfiles)
+ files, err := parseFiles(d.fs, fset, relpath, abspath, pkgfiles)
if err != nil {
info.Err = err
return info
@@ -170,11 +176,11 @@
// collect examples
testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
- files, err = h.c.parseFiles(fset, relpath, abspath, testfiles)
+ files, err = parseFiles(d.fs, fset, relpath, abspath, testfiles)
if err != nil {
log.Println("parsing examples:", err)
}
- info.Examples = collectExamples(h.c, pkg, files)
+ info.Examples = collectExamples(pkg, files)
info.Bugs = info.PDoc.Notes["BUG"]
} else {
// show source code
@@ -188,37 +194,13 @@
info.IsMain = pkgname == "main"
}
- // get directory information, if any
- var dir *Directory
- if tree, _ := h.c.fsTree.Get(); tree != nil && tree.(*Directory) != nil {
- // directory tree is present; lookup respective directory
- // (may still fail if the file system was updated and the
- // new directory tree has not yet been computed)
- dir = tree.(*Directory).lookup(abspath)
- }
- if dir == nil {
- // TODO(agnivade): handle this case better, now since there is no CLI mode.
- // no directory tree present (happens in command-line mode);
- // compute 2 levels for this page. The second level is to
- // get the synopses of sub-directories.
- // note: cannot use path filter here because in general
- // it doesn't contain the FSTree path
- dir = h.c.newDirectory(abspath, 2)
- }
- info.Dirs = dir.listing(true, func(path string) bool { return h.includePath(path, mode) })
+ info.Dirs = d.root.lookup(abspath).listing(func(path string) bool { return d.includePath(path, mode) })
info.DirFlat = mode&FlatDir != 0
return info
}
-func (h *handlerServer) includePath(path string, mode PageInfoMode) (r bool) {
- // if the path is under one of the exclusion paths, don't list.
- for _, e := range h.exclude {
- if strings.HasPrefix(path, e) {
- return false
- }
- }
-
+func (d *DocTree) includePath(path string, mode PageInfoMode) (r bool) {
// if the path includes 'internal', don't list unless we are in the NoFiltering mode.
if mode&NoFiltering != 0 {
return true
@@ -239,27 +221,23 @@
func (s funcsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s funcsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
-func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if redirect(w, r) {
return
}
- relpath := pathpkg.Clean(r.URL.Path[len(h.stripPrefix)+1:])
+ // TODO(rsc): URL should be clean already.
+ relpath := pathpkg.Clean(strings.TrimPrefix(r.URL.Path, "/pkg/"))
- if !h.corpusInitialized() {
- h.p.ServeError(w, r, relpath, errors.New("Scan is not yet complete. Please retry after a few moments"))
- return
- }
-
- abspath := pathpkg.Join(h.fsRoot, relpath)
+ abspath := pathpkg.Join("/src", relpath)
mode := GetPageInfoMode(r.FormValue("m"))
- if relpath == builtinPkgPath {
+ if relpath == "builtin" {
// The fake built-in package contains unexported identifiers,
// but we want to show them. Also, disable type association,
// since it's not helpful for this fake package (see issue 6645).
mode |= NoFiltering | NoTypeAssoc
}
- info := h.GetPageInfo(abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH"))
+ info := h.d.GetPageInfo(abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH"))
if info.Err != nil {
log.Print(info.Err)
h.p.ServeError(w, r, relpath, info.Err)
@@ -316,48 +294,45 @@
})
}
-func (h *handlerServer) corpusInitialized() bool {
- h.c.initMu.RLock()
- defer h.c.initMu.RUnlock()
- return h.c.initDone
-}
-
type PageInfoMode uint
const (
NoFiltering PageInfoMode = 1 << iota // do not filter exports
+ FlatDir // show directory in a flat (non-indented) manner
AllMethods // show all embedded methods
ShowSource // show source code, do not extract documentation
- FlatDir // show directory in a flat (non-indented) manner
NoTypeAssoc // don't associate consts, vars, and factory functions with types (not exposed via ?m= query parameter, used for package builtin, see issue 6645)
)
// modeNames defines names for each PageInfoMode flag.
-var modeNames = map[string]PageInfoMode{
- "all": NoFiltering,
- "methods": AllMethods,
- "src": ShowSource,
- "flat": FlatDir,
+// The order here must match the order of the constants above.
+var modeNames = []string{
+ "all",
+ "flat",
+ "methods",
+ "src",
}
// generate a query string for persisting PageInfoMode between pages.
-func modeQueryString(mode PageInfoMode) string {
- if modeNames := mode.names(); len(modeNames) > 0 {
- return "?m=" + strings.Join(modeNames, ",")
- }
- return ""
-}
-
-// alphabetically sorted names of active flags for a PageInfoMode.
-func (m PageInfoMode) names() []string {
- var names []string
- for name, mode := range modeNames {
- if m&mode != 0 {
- names = append(names, name)
+func (m PageInfoMode) String() string {
+ s := ""
+ for i, name := range modeNames {
+ if m&(1<<i) != 0 && name != "" {
+ if s != "" {
+ s += ","
+ }
+ s += name
}
}
- sort.Strings(names)
- return names
+ return s
+}
+
+func modeQueryString(m PageInfoMode) string {
+ s := m.String()
+ if s == "" {
+ return ""
+ }
+ return "?m=" + s
}
// GetPageInfoMode computes the PageInfoMode flags by analyzing the request
@@ -365,8 +340,11 @@
func GetPageInfoMode(text string) PageInfoMode {
var mode PageInfoMode
for _, k := range strings.Split(text, ",") {
- if m, found := modeNames[strings.TrimSpace(k)]; found {
- mode |= m
+ k = strings.TrimSpace(k)
+ for i, name := range modeNames {
+ if name == k {
+ mode |= 1 << i
+ }
}
}
return mode
@@ -402,7 +380,7 @@
}
// collectExamples collects examples for pkg from testfiles.
-func collectExamples(c *Corpus, pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example {
+func collectExamples(pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example {
var files []*ast.File
for _, f := range testfiles {
files = append(files, f)
@@ -414,8 +392,6 @@
name := stripExampleSuffix(e.Name)
if name == "" || globals[name] {
examples = append(examples, e)
- } else if c.Verbose {
- log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name)
}
}
diff --git a/internal/godoc/server_test.go b/internal/godoc/server_test.go
index ea3f8fb..934850e 100644
--- a/internal/godoc/server_test.go
+++ b/internal/godoc/server_test.go
@@ -23,19 +23,14 @@
packagePath := "github.com/package"
packageComment := "main is documented in an ignored .go file"
- c := NewCorpus(fstest.MapFS{
+ fs := fstest.MapFS{
"src/" + packagePath + "/ignored.go": {Data: []byte(`// +build ignore
// ` + packageComment + `
package main`)},
- })
- srv := &handlerServer{
- p: &Presentation{
- Corpus: c,
- },
- c: c,
}
- pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, NoFiltering, "linux", "amd64")
+ d := NewDocTree(fs)
+ pInfo := d.GetPageInfo("/src/"+packagePath, packagePath, NoFiltering, "linux", "amd64")
if pInfo.PDoc == nil {
t.Error("pInfo.PDoc = nil; want non-nil.")
@@ -57,7 +52,7 @@
func TestIssue5247(t *testing.T) {
const packagePath = "example.com/p"
- c := NewCorpus(fstest.MapFS{
+ fs := fstest.MapFS{
"src/" + packagePath + "/p.go": {Data: []byte(`package p
//line notgen.go:3
@@ -65,13 +60,10 @@
// line 2 should appear
func F()
//line foo.go:100`)}, // No newline at end to check corner cases.
- })
-
- srv := &handlerServer{
- p: &Presentation{Corpus: c},
- c: c,
}
- pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, 0, "linux", "amd64")
+
+ d := NewDocTree(fs)
+ pInfo := d.GetPageInfo("/src/"+packagePath, packagePath, 0, "linux", "amd64")
if got, want := pInfo.PDoc.Funcs[0].Doc, "F doc //line 1 should appear\nline 2 should appear\n"; got != want {
t.Errorf("pInfo.PDoc.Funcs[0].Doc = %q; want %q", got, want)
}