summaryrefslogtreecommitdiff
path: root/src/cmd/godoc/godoc.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/godoc/godoc.go')
-rw-r--r--src/cmd/godoc/godoc.go376
1 files changed, 104 insertions, 272 deletions
diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go
index e5f7a73d4..26814d2fa 100644
--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -20,7 +20,7 @@ import (
"net/http"
"net/url"
"os"
- "path"
+ pathpkg "path"
"path/filepath"
"regexp"
"runtime"
@@ -55,12 +55,9 @@ var (
// file system roots
// TODO(gri) consider the invariant that goroot always end in '/'
- goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
- testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)")
- pkgPath = flag.String("path", "", "additional package directories (colon-separated)")
- filter = flag.String("filter", "", "filter file containing permitted package directory paths")
- filterMin = flag.Int("filter_minutes", 0, "filter file update interval in minutes; disabled if <= 0")
- filterDelay delayTime // actual filter update interval in minutes; usually filterDelay == filterMin, but filterDelay may back off exponentially
+ goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
+ testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)")
+ pkgPath = flag.String("path", "", "additional package directories (colon-separated)")
// layout control
tabwidth = flag.Int("tabwidth", 4, "tab width")
@@ -70,44 +67,42 @@ var (
// search index
indexEnabled = flag.Bool("index", false, "enable search index")
indexFiles = flag.String("index_files", "", "glob pattern specifying index files;"+
- "if not empty, the index is read from these files in sorted order")
+ "if not empty, the index is read from these files in sorted order")
maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
- // file system mapping
- fs FileSystem // the underlying file system for godoc
- fsHttp http.FileSystem // the underlying file system for http
- fsMap Mapping // user-defined mapping
- fsTree RWValue // *Directory tree of packages, updated with each sync
- pathFilter RWValue // filter used when building fsMap directory trees
- fsModified RWValue // timestamp of last call to invalidateIndex
- docMetadata RWValue // mapping from paths to *Metadata
+ // 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
// http handlers
fileServer http.Handler // default file server
- cmdHandler httpHandler
- pkgHandler httpHandler
+ cmdHandler docServer
+ pkgHandler docServer
)
func initHandlers() {
- paths := filepath.SplitList(*pkgPath)
- gorootSrc := filepath.Join(build.Default.GOROOT, "src", "pkg")
- for _, p := range build.Default.SrcDirs() {
- if p != gorootSrc {
- paths = append(paths, p)
+ // Add named directories in -path argument as
+ // subdirectories of src/pkg.
+ for _, p := range filepath.SplitList(*pkgPath) {
+ _, elem := filepath.Split(p)
+ if elem == "" {
+ log.Fatalf("invalid -path argument: %q has no final element", p)
}
+ fs.Bind("/src/pkg/"+elem, OS(p), "/", bindReplace)
}
- fsMap.Init(paths)
- fileServer = http.FileServer(fsHttp)
- cmdHandler = httpHandler{"/cmd/", filepath.Join(*goroot, "src", "cmd"), false}
- pkgHandler = httpHandler{"/pkg/", filepath.Join(*goroot, "src", "pkg"), true}
+ fileServer = http.FileServer(&httpFS{fs})
+ cmdHandler = docServer{"/cmd/", "/src/cmd", false}
+ pkgHandler = docServer{"/pkg/", "/src/pkg", true}
}
func registerPublicHandlers(mux *http.ServeMux) {
mux.Handle(cmdHandler.pattern, &cmdHandler)
mux.Handle(pkgHandler.pattern, &pkgHandler)
mux.HandleFunc("/doc/codewalk/", codewalk)
+ mux.Handle("/doc/play/", fileServer)
mux.HandleFunc("/search", search)
mux.Handle("/robots.txt", fileServer)
mux.HandleFunc("/opensearch.xml", serveSearchDesc)
@@ -115,7 +110,7 @@ func registerPublicHandlers(mux *http.ServeMux) {
}
func initFSTree() {
- dir := newDirectory(filepath.Join(*goroot, *testDir), nil, -1)
+ dir := newDirectory(pathpkg.Join("/", *testDir), -1)
if dir == nil {
log.Println("Warning: FSTree is nil")
return
@@ -125,177 +120,6 @@ func initFSTree() {
}
// ----------------------------------------------------------------------------
-// Directory filters
-
-// isParentOf returns true if p is a parent of (or the same as) q
-// where p and q are directory paths.
-func isParentOf(p, q string) bool {
- n := len(p)
- return strings.HasPrefix(q, p) && (len(q) <= n || q[n] == '/')
-}
-
-func setPathFilter(list []string) {
- if len(list) == 0 {
- pathFilter.set(nil)
- return
- }
-
- // len(list) > 0
- pathFilter.set(func(path string) bool {
- // list is sorted in increasing order and for each path all its children are removed
- i := sort.Search(len(list), func(i int) bool { return list[i] > path })
- // Now we have list[i-1] <= path < list[i].
- // Path may be a child of list[i-1] or a parent of list[i].
- return i > 0 && isParentOf(list[i-1], path) || i < len(list) && isParentOf(path, list[i])
- })
-}
-
-func getPathFilter() func(string) bool {
- f, _ := pathFilter.get()
- if f != nil {
- return f.(func(string) bool)
- }
- return nil
-}
-
-// readDirList reads a file containing a newline-separated list
-// of directory paths and returns the list of paths.
-func readDirList(filename string) ([]string, error) {
- contents, err := ReadFile(fs, filename)
- if err != nil {
- return nil, err
- }
- // create a sorted list of valid directory names
- filter := func(path string) bool {
- d, e := fs.Lstat(path)
- if e != nil && err == nil {
- // remember first error and return it from readDirList
- // so we have at least some information if things go bad
- err = e
- }
- return e == nil && isPkgDir(d)
- }
- list := canonicalizePaths(strings.Split(string(contents), "\n"), filter)
- // for each parent path, remove all its children q
- // (requirement for binary search to work when filtering)
- i := 0
- for _, q := range list {
- if i == 0 || !isParentOf(list[i-1], q) {
- list[i] = q
- i++
- }
- }
- return list[0:i], err
-}
-
-// updateMappedDirs computes the directory tree for
-// each user-defined file system mapping. If a filter
-// is provided, it is used to filter directories.
-//
-func updateMappedDirs(filter func(string) bool) {
- if !fsMap.IsEmpty() {
- fsMap.Iterate(func(path string, value *RWValue) bool {
- value.set(newDirectory(path, filter, -1))
- return true
- })
- invalidateIndex()
- }
-}
-
-func updateFilterFile() {
- updateMappedDirs(nil) // no filter for accuracy
-
- // collect directory tree leaf node paths
- var buf bytes.Buffer
- fsMap.Iterate(func(_ string, value *RWValue) bool {
- v, _ := value.get()
- if v != nil && v.(*Directory) != nil {
- v.(*Directory).writeLeafs(&buf)
- }
- return true
- })
-
- // update filter file
- if err := writeFileAtomically(*filter, buf.Bytes()); err != nil {
- log.Printf("writeFileAtomically(%s): %s", *filter, err)
- filterDelay.backoff(24 * time.Hour) // back off exponentially, but try at least once a day
- } else {
- filterDelay.set(*filterMin) // revert to regular filter update schedule
- }
-}
-
-func initDirTrees() {
- // setup initial path filter
- if *filter != "" {
- list, err := readDirList(*filter)
- if err != nil {
- log.Printf("readDirList(%s): %s", *filter, err)
- }
- if *verbose || len(list) == 0 {
- log.Printf("found %d directory paths in file %s", len(list), *filter)
- }
- setPathFilter(list)
- }
-
- go updateMappedDirs(getPathFilter()) // use filter for speed
-
- // start filter update goroutine, if enabled.
- if *filter != "" && *filterMin > 0 {
- filterDelay.set(time.Duration(*filterMin) * time.Minute) // initial filter update delay
- go func() {
- for {
- if *verbose {
- log.Printf("start update of %s", *filter)
- }
- updateFilterFile()
- delay, _ := filterDelay.get()
- dt := delay.(time.Duration)
- if *verbose {
- log.Printf("next filter update in %s", dt)
- }
- time.Sleep(dt)
- }
- }()
- }
-}
-
-// ----------------------------------------------------------------------------
-// Path mapping
-
-// Absolute paths are file system paths (backslash-separated on Windows),
-// but relative paths are always slash-separated.
-
-func absolutePath(relpath, defaultRoot string) string {
- abspath := fsMap.ToAbsolute(relpath)
- if abspath == "" {
- // no user-defined mapping found; use default mapping
- abspath = filepath.Join(defaultRoot, filepath.FromSlash(relpath))
- }
- return abspath
-}
-
-func relativeURL(abspath string) string {
- relpath := fsMap.ToRelative(abspath)
- if relpath == "" {
- // prefix must end in a path separator
- prefix := *goroot
- if len(prefix) > 0 && prefix[len(prefix)-1] != filepath.Separator {
- prefix += string(filepath.Separator)
- }
- if strings.HasPrefix(abspath, prefix) {
- // no user-defined mapping found; use default mapping
- relpath = filepath.ToSlash(abspath[len(prefix):])
- }
- }
- // Only if path is an invalid absolute path is relpath == ""
- // at this point. This should never happen since absolute paths
- // are only created via godoc for files that do exist. However,
- // it is ok to return ""; it will simply provide a link to the
- // top of the pkg or src directories.
- return relpath
-}
-
-// ----------------------------------------------------------------------------
// Tab conversion
var spaces = []byte(" ") // 32 spaces seems like a good number
@@ -391,7 +215,7 @@ func writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
}
func filenameFunc(path string) string {
- _, localname := filepath.Split(path)
+ _, localname := pathpkg.Split(path)
return localname
}
@@ -581,7 +405,7 @@ func splitExampleName(s string) (name, suffix string) {
}
func pkgLinkFunc(path string) string {
- relpath := relativeURL(path)
+ relpath := path[1:]
// because of the irregular mapping under goroot
// we need to correct certain relative paths
if strings.HasPrefix(relpath, "src/pkg/") {
@@ -597,7 +421,7 @@ func posLink_urlFunc(node ast.Node, fset *token.FileSet) string {
if p := node.Pos(); p.IsValid() {
pos := fset.Position(p)
- relpath = relativeURL(pos.Filename)
+ relpath = pos.Filename
line = pos.Line
low = pos.Offset
}
@@ -626,6 +450,10 @@ func posLink_urlFunc(node ast.Node, fset *token.FileSet) string {
return buf.String()
}
+func srcLinkFunc(s string) string {
+ return pathpkg.Clean("/" + s)
+}
+
// fmap describes the template functions installed with all godoc templates.
// Convention: template function names ending in "_html" or "_url" produce
// HTML- or URL-escaped strings; all other function results may
@@ -652,7 +480,7 @@ var fmap = template.FuncMap{
// support for URL attributes
"pkgLink": pkgLinkFunc,
- "srcLink": relativeURL,
+ "srcLink": srcLinkFunc,
"posLink_url": posLink_urlFunc,
// formatting of Examples
@@ -662,10 +490,10 @@ var fmap = template.FuncMap{
}
func readTemplate(name string) *template.Template {
- path := filepath.Join(*goroot, "lib", "godoc", name)
+ path := "lib/godoc/" + name
if *templateDir != "" {
defaultpath := path
- path = filepath.Join(*templateDir, name)
+ path = pathpkg.Join(*templateDir, name)
if _, err := fs.Stat(path); err != nil {
log.Print("readTemplate:", err)
path = defaultpath
@@ -718,20 +546,23 @@ func readTemplates() {
// ----------------------------------------------------------------------------
// Generic HTML wrapper
-func servePage(w http.ResponseWriter, title, subtitle, query string, content []byte) {
+func servePage(w http.ResponseWriter, tabtitle, title, subtitle, query string, content []byte) {
+ if tabtitle == "" {
+ tabtitle = title
+ }
d := struct {
+ Tabtitle string
Title string
Subtitle string
- PkgRoots []string
SearchBox bool
Query string
Version string
Menu []byte
Content []byte
}{
+ tabtitle,
title,
subtitle,
- fsMap.PrefixList(),
*indexEnabled,
query,
runtime.Version(),
@@ -780,6 +611,23 @@ func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin
log.Printf("decoding metadata %s: %v", relpath, err)
}
+ // evaluate as template if indicated
+ if meta.Template {
+ tmpl, err := template.New("main").Funcs(templateFuncs).Parse(string(src))
+ if err != nil {
+ log.Printf("parsing template %s: %v", relpath, err)
+ serveError(w, r, relpath, err)
+ return
+ }
+ var buf bytes.Buffer
+ if err := tmpl.Execute(&buf, nil); err != nil {
+ log.Printf("executing template %s: %v", relpath, err)
+ serveError(w, r, relpath, err)
+ return
+ }
+ src = buf.Bytes()
+ }
+
// if it's the language spec, add tags to EBNF productions
if strings.HasSuffix(abspath, "go_spec.html") {
var buf bytes.Buffer
@@ -787,7 +635,7 @@ func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin
src = buf.Bytes()
}
- servePage(w, meta.Title, meta.Subtitle, "", src)
+ servePage(w, "", meta.Title, meta.Subtitle, "", src)
}
func applyTemplate(t *template.Template, name string, data interface{}) []byte {
@@ -799,7 +647,7 @@ func applyTemplate(t *template.Template, name string, data interface{}) []byte {
}
func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
- canonical := path.Clean(r.URL.Path)
+ canonical := pathpkg.Clean(r.URL.Path)
if !strings.HasSuffix("/", canonical) {
canonical += "/"
}
@@ -820,10 +668,10 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit
var buf bytes.Buffer
buf.WriteString("<pre>")
- FormatText(&buf, src, 1, filepath.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s")))
+ FormatText(&buf, src, 1, pathpkg.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s")))
buf.WriteString("</pre>")
- servePage(w, title+" "+relpath, "", "", buf.Bytes())
+ servePage(w, relpath, title+" "+relpath, "", "", buf.Bytes())
}
func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
@@ -833,13 +681,12 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
list, err := fs.ReadDir(abspath)
if err != nil {
- log.Printf("ReadDir: %s", err)
serveError(w, r, relpath, err)
return
}
contents := applyTemplate(dirlistHTML, "dirlistHTML", list)
- servePage(w, "Directory "+relpath, "", "", contents)
+ servePage(w, relpath, "Directory "+relpath, "", "", contents)
}
func serveFile(w http.ResponseWriter, r *http.Request) {
@@ -856,10 +703,10 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
relpath = m.filePath
}
+ abspath := relpath
relpath = relpath[1:] // strip leading slash
- abspath := absolutePath(relpath, *goroot)
- switch path.Ext(relpath) {
+ switch pathpkg.Ext(relpath) {
case ".html":
if strings.HasSuffix(relpath, "/index.html") {
// We'll show index.html for the directory.
@@ -886,8 +733,8 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
if redirect(w, r) {
return
}
- if index := filepath.Join(abspath, "index.html"); isTextFile(index) {
- serveHTMLDoc(w, r, index, relativeURL(index))
+ if index := pathpkg.Join(abspath, "index.html"); isTextFile(index) {
+ serveHTMLDoc(w, r, index, index)
return
}
serveDirectory(w, r, abspath, relpath)
@@ -992,7 +839,7 @@ func (info *PageInfo) IsEmpty() bool {
return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
}
-type httpHandler struct {
+type docServer struct {
pattern string // url pattern; e.g. "/pkg/"
fsRoot string // file system root to which the pattern is mapped
isPkg bool // true if this handler serves real package documentation (as opposed to command documentation)
@@ -1029,7 +876,7 @@ func inList(name string, list []string) bool {
// directories, PageInfo.Dirs is nil. If a directory read error occurred,
// PageInfo.Err is set to the respective error but the error is not logged.
//
-func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo {
+func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo {
var pkgFiles []string
// If we're showing the default package, restrict to the ones
@@ -1043,7 +890,7 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
// to choose, set ctxt.GOOS and ctxt.GOARCH before
// calling ctxt.ScanDir.
ctxt := build.Default
- ctxt.IsAbsPath = path.IsAbs
+ ctxt.IsAbsPath = pathpkg.IsAbs
ctxt.ReadDir = fsReadDir
ctxt.OpenFile = fsOpenFile
dir, err := ctxt.ImportDir(abspath, 0)
@@ -1091,13 +938,13 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
// the package with dirname, and the 3rd choice is a package
// that is not called "main" if there is exactly one such
// package. Otherwise, don't select a package.
- dirpath, dirname := filepath.Split(abspath)
+ dirpath, dirname := pathpkg.Split(abspath)
// If the dirname is "go" we might be in a sub-directory for
// .go files - use the outer directory name instead for better
// results.
if dirname == "go" {
- _, dirname = filepath.Split(filepath.Clean(dirpath))
+ _, dirname = pathpkg.Split(pathpkg.Clean(dirpath))
}
var choice3 *ast.Package
@@ -1161,7 +1008,7 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
if mode&allMethods != 0 {
m |= doc.AllMethods
}
- pdoc = doc.New(pkg, path.Clean(relpath), m) // no trailing '/' in importpath
+ pdoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath
} else {
// show source code
// TODO(gri) Consider eliminating export filtering in this mode,
@@ -1184,34 +1031,11 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
timestamp = ts
}
if dir == nil {
- // the path may refer to a user-specified file system mapped
- // via fsMap; lookup that mapping and corresponding RWValue
- // if any
- var v *RWValue
- fsMap.Iterate(func(path string, value *RWValue) bool {
- if isParentOf(path, abspath) {
- // mapping found
- v = value
- return false
- }
- return true
- })
- if v != nil {
- // found a RWValue associated with a user-specified file
- // system; a non-nil RWValue stores a (possibly out-of-date)
- // directory tree for that file system
- if tree, ts := v.get(); tree != nil && tree.(*Directory) != nil {
- dir = tree.(*Directory).lookup(abspath)
- timestamp = ts
- }
- }
- }
- if dir == nil {
// no directory tree present (too early after startup or
// command-line mode); compute one level for this page
// note: cannot use path filter here because in general
// it doesn't contain the fsTree path
- dir = newDirectory(abspath, nil, 1)
+ dir = newDirectory(abspath, 1)
timestamp = time.Now()
}
@@ -1230,13 +1054,13 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
}
}
-func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if redirect(w, r) {
return
}
- relpath := path.Clean(r.URL.Path[len(h.pattern):])
- abspath := absolutePath(relpath, h.fsRoot)
+ relpath := pathpkg.Clean(r.URL.Path[len(h.pattern):])
+ abspath := pathpkg.Join(h.fsRoot, relpath)
mode := getPageInfoMode(r)
if relpath == builtinPkgPath {
mode = noFiltering
@@ -1254,30 +1078,41 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
- var title, subtitle string
+ var tabtitle, title, subtitle string
switch {
case info.PAst != nil:
- title = "Package " + info.PAst.Name.Name
+ tabtitle = info.PAst.Name.Name
+ title = "Package " + tabtitle
case info.PDoc != nil:
- switch {
- case info.IsPkg:
- title = "Package " + info.PDoc.Name
- case info.PDoc.Name == fakePkgName:
+ if info.PDoc.Name == fakePkgName {
// assume that the directory name is the command name
- _, pkgname := path.Split(relpath)
- title = "Command " + pkgname
- default:
- title = "Command " + info.PDoc.Name
+ _, tabtitle = pathpkg.Split(relpath)
+ } else {
+ tabtitle = info.PDoc.Name
+ }
+ if info.IsPkg {
+ title = "Package " + tabtitle
+ } else {
+ title = "Command " + tabtitle
}
default:
- title = "Directory " + relativeURL(info.Dirname)
+ tabtitle = info.Dirname
+ title = "Directory " + tabtitle
if *showTimestamps {
subtitle = "Last update: " + info.DirTime.String()
}
}
+ // special cases for top-level package/command directories
+ switch tabtitle {
+ case "/src/pkg":
+ tabtitle = "Packages"
+ case "/src/cmd":
+ tabtitle = "Commands"
+ }
+
contents := applyTemplate(packageHTML, "packageHTML", info)
- servePage(w, title, subtitle, "", contents)
+ servePage(w, tabtitle, title, subtitle, "", contents)
}
// ----------------------------------------------------------------------------
@@ -1367,7 +1202,7 @@ func search(w http.ResponseWriter, r *http.Request) {
}
contents := applyTemplate(searchHTML, "searchHTML", result)
- servePage(w, title, "", query, contents)
+ servePage(w, query, title, "", query, contents)
}
// ----------------------------------------------------------------------------
@@ -1376,6 +1211,7 @@ func search(w http.ResponseWriter, r *http.Request) {
type Metadata struct {
Title string
Subtitle string
+ Template bool // execute as template
Path string // canonical path for this page
filePath string // filesystem path relative to goroot
}
@@ -1414,7 +1250,7 @@ func updateMetadata() {
return
}
for _, fi := range fis {
- name := filepath.Join(dir, fi.Name())
+ name := pathpkg.Join(dir, fi.Name())
if fi.IsDir() {
scan(name) // recurse
continue
@@ -1434,7 +1270,7 @@ func updateMetadata() {
continue
}
// Store relative filesystem path in Metadata.
- meta.filePath = filepath.Join("/", name[len(*goroot):])
+ meta.filePath = name
if meta.Path == "" {
// If no Path, canonical path is actual path.
meta.Path = meta.filePath
@@ -1444,7 +1280,7 @@ func updateMetadata() {
metadata[meta.filePath] = &meta
}
}
- scan(filepath.Join(*goroot, "doc"))
+ scan("/doc")
docMetadata.set(metadata)
}
@@ -1519,13 +1355,9 @@ func feedDirnames(root *RWValue, c chan<- string) {
// of all the file systems under godoc's observation.
//
func fsDirnames() <-chan string {
- c := make(chan string, 256) // asynchronous for fewer context switches
+ c := make(chan string, 256) // buffered for fewer context switches
go func() {
feedDirnames(&fsTree, c)
- fsMap.Iterate(func(_ string, root *RWValue) bool {
- feedDirnames(root, c)
- return true
- })
close(c)
}()
return c