From 5883c6ef1f7bd313436ad9e2030a0e0376c3fd94 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 16 Feb 2010 11:20:55 -0800 Subject: [PATCH] godoc support for directories outside $GOROOT Example use: godoc -path=/home/user1:/home/build/foo -http=:6666 will start a local godoc that maps urls starting with /pkg/user1 or /pkg/foo to the respective roots specified in the path. Missing: Handling of overlapping package directories, multiple packages per directory. R=rsc CC=golang-dev https://golang.org/cl/206078 --- lib/godoc/{source.html => error.html} | 10 +- lib/godoc/godoc.html | 3 + lib/godoc/package.html | 10 +- lib/godoc/search.html | 10 +- src/cmd/godoc/Makefile | 1 + src/cmd/godoc/godoc.go | 485 ++++++++++++++------------ src/cmd/godoc/main.go | 29 +- src/cmd/godoc/mapping.go | 50 ++- 8 files changed, 327 insertions(+), 271 deletions(-) rename lib/godoc/{source.html => error.html} (56%) diff --git a/lib/godoc/source.html b/lib/godoc/error.html similarity index 56% rename from lib/godoc/source.html rename to lib/godoc/error.html index 4189f4ef80..c14c574057 100644 --- a/lib/godoc/source.html +++ b/lib/godoc/error.html @@ -4,10 +4,6 @@ license that can be found in the LICENSE file. --> -{.section Error} -

- {@|html} -

-{.or} -
{Source|html}
-{.end} +

+{@|html-esc} +

diff --git a/lib/godoc/godoc.html b/lib/godoc/godoc.html index 8939825007..944643a34f 100644 --- a/lib/godoc/godoc.html +++ b/lib/godoc/godoc.html @@ -94,6 +94,9 @@
  • How to write code
  • Command documentation
  • Package documentation
  • + {.repeated section PkgRoots} +
  • Package documentation for {@|html-esc}
  • + {.end}
  • Source files
  • The Go project
  • diff --git a/lib/godoc/package.html b/lib/godoc/package.html index 6a799c2fd8..2113e46bd8 100644 --- a/lib/godoc/package.html +++ b/lib/godoc/package.html @@ -16,7 +16,7 @@

    Package files

    {.repeated section @} - {@|html} + {@|localname} {.end}

    @@ -38,14 +38,14 @@ {.end} {.section Funcs} {.repeated section @} -

    func {Name|html}

    +

    func {Name|html}

    {Decl|html}

    {Doc|html-comment} {.end} {.end} {.section Types} {.repeated section @} -

    type {Type.Name|html}

    +

    type {Type.Name|html}

    {Doc|html-comment}

    {Decl|html}

    {.repeated section Consts} @@ -57,12 +57,12 @@
    {Decl|html}
    {.end} {.repeated section Factories} -

    func {Name|html}

    +

    func {Name|html}

    {Decl|html}

    {Doc|html-comment} {.end} {.repeated section Methods} -

    func ({Recv|html}) {Name|html}

    +

    func ({Recv|html}) {Name|html}

    {Decl|html}

    {Doc|html-comment} {.end} diff --git a/lib/godoc/search.html b/lib/godoc/search.html index 254f9b66f3..a6b7fe29cd 100644 --- a/lib/godoc/search.html +++ b/lib/godoc/search.html @@ -22,11 +22,11 @@ {.section Decls}

    Package-level declarations

    {.repeated section @} -

    package {Pak.Name|html}

    +

    package {Pak.Name|html}

    {.repeated section Files} {.repeated section Groups} {.repeated section Infos} - {File.Path|html}:{@|infoLine} + {File.Path|html}:{@|infoLine}
    {@|infoSnippet}
    {.end} {.end} @@ -36,9 +36,9 @@ {.section Others}

    Local declarations and uses

    {.repeated section @} -

    package {Pak.Name|html}

    +

    package {Pak.Name|html}

    {.repeated section Files} - {File.Path|html} + {File.Path|html} {.repeated section Groups} @@ -47,7 +47,7 @@ diff --git a/src/cmd/godoc/Makefile b/src/cmd/godoc/Makefile index dcd4e5b4e0..8928221f09 100644 --- a/src/cmd/godoc/Makefile +++ b/src/cmd/godoc/Makefile @@ -9,6 +9,7 @@ GOFILES=\ godoc.go\ index.go\ main.go\ + mapping.go\ snippet.go\ spec.go\ diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index 97ed329fe2..9cc194435e 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -77,26 +77,54 @@ func (dt *delayTime) backoff(max int) { var ( verbose = flag.Bool("v", false, "verbose mode") - // file system roots + // "fixed" file system roots goroot string - cmdroot = flag.String("cmdroot", "src/cmd", "root command source directory (if unrooted, relative to goroot)") - pkgroot = flag.String("pkgroot", "src/pkg", "root package source directory (if unrooted, relative to goroot)") - tmplroot = flag.String("tmplroot", "lib/godoc", "root template directory (if unrooted, relative to goroot)") + cmdroot string + pkgroot string + tmplroot string + + // additional file system roots to consider + path = flag.String("path", "", "additional pkg directories") // layout control tabwidth = flag.Int("tabwidth", 4, "tab width") + + // file system mapping + fsMap Mapping // user-defined mapping + fsTree RWValue // *Directory tree of packages, updated with each sync + + // http handlers + fileServer http.Handler // default file server + cmdHandler httpHandler + pkgHandler httpHandler ) -var fsTree RWValue // *Directory tree of packages, updated with each sync - - -func init() { +func initRoots() { goroot = os.Getenv("GOROOT") if goroot == "" { goroot = pathutil.Join(os.Getenv("HOME"), "go") } flag.StringVar(&goroot, "goroot", goroot, "Go root directory") + + // other flags/variables that depend on goroot + flag.StringVar(&cmdroot, "cmdroot", pathutil.Join(goroot, "src/cmd"), "command source directory") + flag.StringVar(&pkgroot, "pkgroot", pathutil.Join(goroot, "src/pkg"), "package source directory") + flag.StringVar(&tmplroot, "tmplroot", pathutil.Join(goroot, "lib/godoc"), "template directory") + + fsMap.Init(*path) + fileServer = http.FileServer(goroot, "") + + cmdHandler = httpHandler{"/cmd/", cmdroot, false} + pkgHandler = httpHandler{"/pkg/", pkgroot, true} +} + + +func registerPublicHandlers(mux *http.ServeMux) { + mux.Handle(cmdHandler.pattern, &cmdHandler) + mux.Handle(pkgHandler.pattern, &pkgHandler) + mux.Handle("/search", http.HandlerFunc(search)) + mux.Handle("/", http.HandlerFunc(serveFile)) } @@ -173,6 +201,31 @@ func firstSentence(s string) string { } +func absolutePath(path, defaultRoot string) string { + abspath := fsMap.ToAbsolute(path) + if abspath == "" { + // no user-defined mapping found; use default mapping + abspath = pathutil.Join(defaultRoot, path) + } + return abspath +} + + +func relativePath(path string) string { + relpath := fsMap.ToRelative(path) + if relpath == "" && strings.HasPrefix(path, goroot+"/") { + // no user-defined mapping found; use default mapping + relpath = path[len(goroot)+1:] + } + // 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 +} + + // ---------------------------------------------------------------------------- // Package directories @@ -287,26 +340,32 @@ func (dir *Directory) iter(skipRoot bool) <-chan *Directory { } +func (dir *Directory) lookupLocal(name string) *Directory { + for _, d := range dir.Dirs { + if d.Name == name { + return d + } + } + return nil +} + + // lookup looks for the *Directory for a given path, relative to dir. func (dir *Directory) lookup(path string) *Directory { - path = pathutil.Clean(path) // no trailing '/' - - if dir == nil || path == "" || path == "." { - return dir - } - - dpath, dname := pathutil.Split(path) - if dpath == "" { - // directory-local name - for _, d := range dir.Dirs { - if dname == d.Name { - return d - } + d := strings.Split(dir.Path, "/", 0) + p := strings.Split(path, "/", 0) + i := 0 + for i < len(d) { + if i >= len(p) || d[i] != p[i] { + return nil } - return nil + i++ } - - return dir.lookup(dpath).lookup(dname) + for dir != nil && i < len(p) { + dir = dir.lookupLocal(p[i]) + i++ + } + return dir } @@ -383,20 +442,6 @@ func (root *Directory) listing(skipRoot bool) *DirList { } -func listing(dirs []*os.Dir) *DirList { - list := make([]DirEntry, len(dirs)+1) - list[0] = DirEntry{0, 1, "..", "..", ""} - for i, d := range dirs { - p := &list[i+1] - p.Depth = 0 - p.Height = 1 - p.Path = d.Name - p.Name = d.Name - } - return &DirList{1, list} -} - - // ---------------------------------------------------------------------------- // HTML formatting support @@ -552,12 +597,6 @@ func writeText(w io.Writer, text []byte, html bool) { } -type StyledNode struct { - node interface{} - styler printer.Styler -} - - // Write anything to w; optionally html-escaped. func writeAny(w io.Writer, x interface{}, html bool) { switch v := x.(type) { @@ -567,8 +606,6 @@ func writeAny(w io.Writer, x interface{}, html bool) { writeText(w, strings.Bytes(v), html) case ast.Decl, ast.Expr, ast.Stmt, *ast.File: writeNode(w, x, html, &defaultStyler) - case StyledNode: - writeNode(w, v.node, html, v.styler) default: if html { var buf bytes.Buffer @@ -609,36 +646,51 @@ func textFmt(w io.Writer, x interface{}, format string) { } -func removePrefix(s, prefix string) string { - if strings.HasPrefix(s, prefix) { - return s[len(prefix):] - } - return s -} +// Template formatter for the various "url-xxx" formats. +func urlFmt(w io.Writer, x interface{}, format string) { + var path string + var line int - -// Template formatter for "path" format. -func pathFmt(w io.Writer, x interface{}, format string) { - // TODO(gri): Need to find a better solution for this. - // This will not work correctly if *cmdroot - // or *pkgroot change. - writeAny(w, removePrefix(x.(string), "src"), true) -} - - -// Template formatter for "link" format. -func linkFmt(w io.Writer, x interface{}, format string) { - type Positioner interface { + // determine path and position info, if any + type positioner interface { Pos() token.Position } - if node, ok := x.(Positioner); ok { - pos := node.Pos() + switch t := x.(type) { + case string: + path = t + case positioner: + pos := t.Pos() if pos.IsValid() { - // line id's in html-printed source are of the - // form "L%d" where %d stands for the line number - fmt.Fprintf(w, "/%s#L%d", htmlEscape(pos.Filename), pos.Line) + path = pos.Filename + line = pos.Line } } + + // map path + relpath := relativePath(path) + + // convert to URL + switch format { + default: + // we should never reach here, but be resilient + // and assume the url-pkg format instead + log.Stderrf("INTERNAL ERROR: urlFmt(%s)", format) + fallthrough + case "url-pkg": + // because of the irregular mapping under goroot + // we need to correct certain relative paths + if strings.HasPrefix(relpath, "src/pkg/") { + relpath = relpath[len("src/pkg/"):] + } + template.HTMLEscape(w, strings.Bytes(pkgHandler.pattern+relpath)) + case "url-src": + template.HTMLEscape(w, strings.Bytes("/"+relpath)) + case "url-pos": + // line id's in html-printed source are of the + // form "L%d" where %d stands for the line number + template.HTMLEscape(w, strings.Bytes("/"+relpath)) + fmt.Fprintf(w, "#L%d", line) + } } @@ -710,24 +762,33 @@ func dirslashFmt(w io.Writer, x interface{}, format string) { } +// Template formatter for "localname" format. +func localnameFmt(w io.Writer, x interface{}, format string) { + _, localname := pathutil.Split(x.(string)) + template.HTMLEscape(w, strings.Bytes(localname)) +} + + var fmap = template.FormatterMap{ "": textFmt, "html": htmlFmt, "html-esc": htmlEscFmt, "html-comment": htmlCommentFmt, - "path": pathFmt, - "link": linkFmt, + "url-pkg": urlFmt, + "url-src": urlFmt, + "url-pos": urlFmt, "infoKind": infoKindFmt, "infoLine": infoLineFmt, "infoSnippet": infoSnippetFmt, "padding": paddingFmt, "time": timeFmt, "dir/": dirslashFmt, + "localname": localnameFmt, } func readTemplate(name string) *template.Template { - path := pathutil.Join(*tmplroot, name) + path := pathutil.Join(tmplroot, name) data, err := ioutil.ReadFile(path) if err != nil { log.Exitf("ReadFile %s: %v", path, err) @@ -742,22 +803,21 @@ func readTemplate(name string) *template.Template { var ( dirlistHTML, + errorHTML, godocHTML, packageHTML, packageText, - searchHTML, - sourceHTML *template.Template + searchHTML *template.Template ) func readTemplates() { - // have to delay until after flags processing, - // so that main has chdir'ed to goroot. + // have to delay until after flags processing, so that tmplroot is known dirlistHTML = readTemplate("dirlist.html") + errorHTML = readTemplate("error.html") godocHTML = readTemplate("godoc.html") packageHTML = readTemplate("package.html") packageText = readTemplate("package.txt") searchHTML = readTemplate("search.html") - sourceHTML = readTemplate("source.html") } @@ -767,6 +827,7 @@ func readTemplates() { func servePage(c *http.Conn, title, query string, content []byte) { type Data struct { Title string + PkgRoots []string Timestamp uint64 // int64 to be compatible with os.Dir.Mtime_ns Query string Content []byte @@ -775,6 +836,7 @@ func servePage(c *http.Conn, title, query string, content []byte) { _, ts := fsTree.get() d := Data{ Title: title, + PkgRoots: fsMap.PrefixList(), Timestamp: uint64(ts) * 1e9, // timestamp in ns Query: query, Content: content, @@ -787,7 +849,7 @@ func servePage(c *http.Conn, title, query string, content []byte) { func serveText(c *http.Conn, text []byte) { - c.SetHeader("content-type", "text/plain; charset=utf-8") + c.SetHeader("Content-Type", "text/plain; charset=utf-8") c.Write(text) } @@ -811,12 +873,18 @@ func commentText(src []byte) (text string) { } -func serveHTMLDoc(c *http.Conn, r *http.Request, path string) { +func serveError(c *http.Conn, r *http.Request, relpath string, err os.Error) { + contents := applyTemplate(errorHTML, "errorHTML", err) + servePage(c, "File "+relpath, "", contents) +} + + +func serveHTMLDoc(c *http.Conn, r *http.Request, abspath, relpath string) { // get HTML body contents - src, err := ioutil.ReadFile(path) + src, err := ioutil.ReadFile(abspath) if err != nil { - log.Stderrf("%v", err) - http.NotFound(c, r) + log.Stderrf("ioutil.ReadFile: %s", err) + serveError(c, r, relpath, err) return } @@ -828,7 +896,7 @@ func serveHTMLDoc(c *http.Conn, r *http.Request, path string) { } // if it's the language spec, add tags to EBNF productions - if strings.HasSuffix(path, "go_spec.html") { + if strings.HasSuffix(abspath, "go_spec.html") { var buf bytes.Buffer linkify(&buf, src) src = buf.Bytes() @@ -839,24 +907,29 @@ func serveHTMLDoc(c *http.Conn, r *http.Request, path string) { } -func serveGoSource(c *http.Conn, r *http.Request, path string) { - var info struct { - Source StyledNode - Error string +func applyTemplate(t *template.Template, name string, data interface{}) []byte { + var buf bytes.Buffer + if err := t.Execute(data, &buf); err != nil { + log.Stderrf("%s.Execute: %s", name, err) } + return buf.Bytes() +} - file, err := parser.ParseFile(path, nil, nil, parser.ParseComments) - info.Source = StyledNode{file, &Styler{linetags: true, highlight: r.FormValue("h")}} + +func serveGoSource(c *http.Conn, r *http.Request, abspath, relpath string) { + file, err := parser.ParseFile(abspath, nil, nil, parser.ParseComments) if err != nil { - info.Error = err.String() + log.Stderrf("parser.ParseFile: %s", err) + serveError(c, r, relpath, err) + return } var buf bytes.Buffer - if err := sourceHTML.Execute(info, &buf); err != nil { - log.Stderrf("sourceHTML.Execute: %s", err) - } + fmt.Fprintln(&buf, "
    ")
    +	writeNode(&buf, file, true, &Styler{linetags: true, highlight: r.FormValue("h")})
    +	fmt.Fprintln(&buf, "
    ") - servePage(c, "Source file "+path, "", buf.Bytes()) + servePage(c, "Source file "+relpath, "", buf.Bytes()) } @@ -916,10 +989,12 @@ func isTextFile(path string) bool { } -func serveTextFile(c *http.Conn, r *http.Request, path string) { - src, err := ioutil.ReadFile(path) +func serveTextFile(c *http.Conn, r *http.Request, abspath, relpath string) { + src, err := ioutil.ReadFile(abspath) if err != nil { - log.Stderrf("serveTextFile: %s", err) + log.Stderrf("ioutil.ReadFile: %s", err) + serveError(c, r, relpath, err) + return } var buf bytes.Buffer @@ -927,18 +1002,19 @@ func serveTextFile(c *http.Conn, r *http.Request, path string) { template.HTMLEscape(&buf, src) fmt.Fprintln(&buf, "") - servePage(c, "Text file "+path, "", buf.Bytes()) + servePage(c, "Text file "+relpath, "", buf.Bytes()) } -func serveDirectory(c *http.Conn, r *http.Request, path string) { +func serveDirectory(c *http.Conn, r *http.Request, abspath, relpath string) { if redirect(c, r) { return } - list, err := ioutil.ReadDir(path) + list, err := ioutil.ReadDir(abspath) if err != nil { - http.NotFound(c, r) + log.Stderrf("ioutil.ReadDir: %s", err) + serveError(c, r, relpath, err) return } @@ -948,49 +1024,47 @@ func serveDirectory(c *http.Conn, r *http.Request, path string) { } } - var buf bytes.Buffer - if err := dirlistHTML.Execute(list, &buf); err != nil { - log.Stderrf("dirlistHTML.Execute: %s", err) - } - - servePage(c, "Directory "+path, "", buf.Bytes()) + contents := applyTemplate(dirlistHTML, "dirlistHTML", list) + servePage(c, "Directory "+relpath, "", contents) } -var fileServer = http.FileServer(".", "") - func serveFile(c *http.Conn, r *http.Request) { - path := pathutil.Join(".", r.URL.Path) + relpath := r.URL.Path[1:] // serveFile URL paths start with '/' + abspath := absolutePath(relpath, goroot) // pick off special cases and hand the rest to the standard file server - switch ext := pathutil.Ext(path); { - case r.URL.Path == "/": - serveHTMLDoc(c, r, "doc/root.html") + switch r.URL.Path { + case "/": + serveHTMLDoc(c, r, pathutil.Join(goroot, "doc/root.html"), "doc/root.html") return - case r.URL.Path == "/doc/root.html": + case "/doc/root.html": // hide landing page from its real name - http.NotFound(c, r) + http.Redirect(c, "/", http.StatusMovedPermanently) return + } - case ext == ".html": - if strings.HasSuffix(path, "/index.html") { + switch pathutil.Ext(abspath) { + case ".html": + if strings.HasSuffix(abspath, "/index.html") { // We'll show index.html for the directory. // Use the dir/ version as canonical instead of dir/index.html. http.Redirect(c, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently) return } - serveHTMLDoc(c, r, path) + serveHTMLDoc(c, r, abspath, relpath) return - case ext == ".go": - serveGoSource(c, r, path) + case ".go": + serveGoSource(c, r, abspath, relpath) return } - dir, err := os.Lstat(path) + dir, err := os.Lstat(abspath) if err != nil { - http.NotFound(c, r) + log.Stderr(err) + serveError(c, r, abspath, err) return } @@ -998,16 +1072,16 @@ func serveFile(c *http.Conn, r *http.Request) { if redirect(c, r) { return } - if index := path + "/index.html"; isTextFile(index) { - serveHTMLDoc(c, r, index) + if index := abspath + "/index.html"; isTextFile(index) { + serveHTMLDoc(c, r, index, relativePath(index)) return } - serveDirectory(c, r, path) + serveDirectory(c, r, abspath, relpath) return } - if isTextFile(path) { - serveTextFile(c, r, path) + if isTextFile(abspath) { + serveTextFile(c, r, abspath, relpath) return } @@ -1018,14 +1092,16 @@ func serveFile(c *http.Conn, r *http.Request) { // ---------------------------------------------------------------------------- // Packages -// Package name used for commands that have non-identifier names. +// Fake package file and name for commands. Contains the command documentation. +const fakePkgFile = "doc.go" const fakePkgName = "documentation" type PageInfo struct { - PDoc *doc.PackageDoc // nil if no package found - Dirs *DirList // nil if no directory information found - IsPkg bool // false if this is not documenting a real package + Dirname string // directory containing the package + PDoc *doc.PackageDoc // nil if no package found + Dirs *DirList // nil if no directory information found + IsPkg bool // false if this is not documenting a real package } @@ -1036,62 +1112,22 @@ type httpHandler struct { } -// getPageInfo returns the PageInfo for a package directory path. If the -// parameter try is true, no errors are logged if getPageInfo fails. -// If there is no corresponding package in the directory, -// PageInfo.PDoc is nil. If there are no subdirectories, -// PageInfo.Dirs is nil. +// getPageInfo returns the PageInfo for a package directory path. If +// the parameter try is true, no errors are logged if getPageInfo fails. +// If there is no corresponding package in the directory, PageInfo.PDoc +// is nil. If there are no subdirectories, PageInfo.Dirs is nil. // -func (h *httpHandler) getPageInfo(path string, try bool) PageInfo { - var dirname string - // If the path starts with a slash or ., ignore $GOROOT. - // It would be nice to handle "./dir" too, but godoc chdirs to $GOROOT. TODO: fix. - if len(path) > 0 && path[0] == '/' { - dirname = path - // --- Start of hack - } else if len(path) > 0 && path[0] == '.' && workingDir != "" { - path = pathutil.Join(workingDir, path) - dirname = path - // --- End of hack - } else { - // the path is relative to h.fsroot - dirname = pathutil.Join(h.fsRoot, path) - } - - // the package name is the directory name within its parent - // (use dirname instead of path because dirname is clean; i.e. has no trailing '/') - _, pkgname := pathutil.Split(dirname) +func (h *httpHandler) getPageInfo(relpath string, try bool) PageInfo { + dirname := absolutePath(relpath, h.fsRoot) // filter function to select the desired .go files filter := func(d *os.Dir) bool { - if isPkgFile(d) { - - // --- Start of hack. - // An ugly special case: If the path is rooted, just say - // yes in the hope we'll get some output from a directory - // outside $GOROOT. Ugly but effective for command-line - // output but may not find everything if there are multiple - // packages in the directory, since godoc assumes one - // package per directory. - // TODO: Do this better. - if len(path) > 0 && path[0] == '/' { - return true - } - // --- End of hack. - - // Some directories contain main packages: Only accept - // files that belong to the expected package so that - // parser.ParsePackage doesn't return "multiple packages - // found" errors. - // Additionally, accept the special package name - // fakePkgName if we are looking at cmd documentation. - name := pkgName(dirname + "/" + d.Name) - return name == pkgname || h.fsRoot == *cmdroot && name == fakePkgName - } - return false + // If we are looking at cmd documentation, only accept + // the special fakePkgFile containing the documentation. + return isPkgFile(d) && (h.isPkg || d.Name == fakePkgFile) } - // get package AST + // get package ASTs pkgs, err := parser.ParseDir(dirname, filter, parser.ParseComments) if err != nil && !try { // TODO: errors should be shown instead of an empty directory @@ -1101,16 +1137,29 @@ func (h *httpHandler) getPageInfo(path string, try bool) PageInfo { // TODO: should handle multiple packages log.Stderrf("parser.parseDir: found %d packages", len(pkgs)) } + + // Get the best matching package: either the first one, or the + // first one whose package name matches the directory name. + // The package name is the directory name within its parent + // (use dirname instead of path because dirname is clean; i.e. + // has no trailing '/'). + _, pkgname := pathutil.Split(dirname) var pkg *ast.Package - for _, pkg = range pkgs { - break // take the first package found + for _, p := range pkgs { + switch { + case pkg == nil: + pkg = p + case p.Name == pkgname: + pkg = p + break + } } // compute package documentation var pdoc *doc.PackageDoc if pkg != nil { ast.PackageExports(pkg) - pdoc = doc.NewPackageDoc(pkg, pathutil.Clean(path)) // no trailing '/' in importpath + pdoc = doc.NewPackageDoc(pkg, pathutil.Clean(relpath)) // no trailing '/' in importpath } // get directory information @@ -1118,15 +1167,18 @@ func (h *httpHandler) getPageInfo(path string, try bool) PageInfo { if tree, _ := fsTree.get(); tree != 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 beet computed) + // new directory tree has not yet been computed) + // TODO(gri) Need to build directory tree for fsMap entries dir = tree.(*Directory).lookup(dirname) - } else { + } + if dir == nil { // no directory tree present (either early after startup - // or command-line mode); compute one level for this page + // or command-line mode, or we don't build a tree for the + // directory; e.g. google3); compute one level for this page dir = newDirectory(dirname, 1) } - return PageInfo{pdoc, dir.listing(true), h.isPkg} + return PageInfo{dirname, pdoc, dir.listing(true), h.isPkg} } @@ -1135,41 +1187,33 @@ func (h *httpHandler) ServeHTTP(c *http.Conn, r *http.Request) { return } - path := r.URL.Path - path = path[len(h.pattern):] - info := h.getPageInfo(path, false) + relpath := r.URL.Path[len(h.pattern):] + info := h.getPageInfo(relpath, false) - var buf bytes.Buffer if r.FormValue("f") == "text" { - if err := packageText.Execute(info, &buf); err != nil { - log.Stderrf("packageText.Execute: %s", err) - } - serveText(c, buf.Bytes()) + contents := applyTemplate(packageText, "packageText", info) + serveText(c, contents) return } - if err := packageHTML.Execute(info, &buf); err != nil { - log.Stderrf("packageHTML.Execute: %s", err) - } - - if path == "" { - path = "." // don't display an empty path - } - title := "Directory " + path + var title string if info.PDoc != nil { switch { case h.isPkg: title = "Package " + info.PDoc.PackageName case info.PDoc.PackageName == fakePkgName: // assume that the directory name is the command name - _, pkgname := pathutil.Split(pathutil.Clean(path)) + _, pkgname := pathutil.Split(pathutil.Clean(relpath)) title = "Command " + pkgname default: title = "Command " + info.PDoc.PackageName } + } else { + title = "Directory " + relativePath(info.Dirname) } - servePage(c, title, "", buf.Bytes()) + contents := applyTemplate(packageHTML, "packageHTML", info) + servePage(c, title, "", contents) } @@ -1197,11 +1241,6 @@ func search(c *http.Conn, r *http.Request) { result.Accurate = timestamp >= ts } - var buf bytes.Buffer - if err := searchHTML.Execute(result, &buf); err != nil { - log.Stderrf("searchHTML.Execute: %s", err) - } - var title string if result.Hit != nil { title = fmt.Sprintf(`Results for query %q`, query) @@ -1209,28 +1248,14 @@ func search(c *http.Conn, r *http.Request) { title = fmt.Sprintf(`No results found for query %q`, query) } - servePage(c, title, query, buf.Bytes()) + contents := applyTemplate(searchHTML, "searchHTML", result) + servePage(c, title, query, contents) } // ---------------------------------------------------------------------------- -// Server +// Indexer -var ( - cmdHandler = httpHandler{"/cmd/", *cmdroot, false} - pkgHandler = httpHandler{"/pkg/", *pkgroot, true} -) - - -func registerPublicHandlers(mux *http.ServeMux) { - mux.Handle(cmdHandler.pattern, &cmdHandler) - mux.Handle(pkgHandler.pattern, &pkgHandler) - mux.Handle("/search", http.HandlerFunc(search)) - mux.Handle("/", http.HandlerFunc(serveFile)) -} - - -// Indexing goroutine. func indexer() { for { _, ts := fsTree.get() @@ -1240,7 +1265,7 @@ func indexer() { // from the sync goroutine, but this solution is // more decoupled, trivial, and works well enough) start := time.Nanoseconds() - index := NewIndex(".") + index := NewIndex(goroot) stop := time.Nanoseconds() searchIndex.set(index) if *verbose { diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index f475d8fa10..ef08551ce7 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -47,9 +47,6 @@ var ( // layout control html = flag.Bool("html", false, "print HTML in command-line mode") - - // --- Hack to remember current directory - workingDir string ) @@ -112,7 +109,7 @@ func dosync(c *http.Conn, r *http.Request) { // TODO(gri): The directory tree may be temporarily out-of-sync. // Consider keeping separate time stamps so the web- // page can indicate this discrepancy. - fsTree.set(newDirectory(".", maxDirDepth)) + fsTree.set(newDirectory(goroot, maxDirDepth)) fallthrough case 1: // sync failed because no files changed; @@ -155,17 +152,7 @@ func main() { log.Exitf("negative tabwidth %d", *tabwidth) } - // --- Start of hack. - // Remember where we were, so "." works as a directory name. - // Error's not worth worrying about; we just check for empty string - // when we need it. - workingDir, _ = os.Getwd() - // --- End of hack. - - if err := os.Chdir(goroot); err != nil { - log.Exitf("chdir %s: %v", goroot, err) - } - + initRoots() readTemplates() if *httpaddr != "" { @@ -175,10 +162,14 @@ func main() { log.Stderrf("Go Documentation Server\n") log.Stderrf("address = %s\n", *httpaddr) log.Stderrf("goroot = %s\n", goroot) - log.Stderrf("cmdroot = %s\n", *cmdroot) - log.Stderrf("pkgroot = %s\n", *pkgroot) - log.Stderrf("tmplroot = %s\n", *tmplroot) + log.Stderrf("cmdroot = %s\n", cmdroot) + log.Stderrf("pkgroot = %s\n", pkgroot) + log.Stderrf("tmplroot = %s\n", tmplroot) log.Stderrf("tabwidth = %d\n", *tabwidth) + if !fsMap.IsEmpty() { + log.Stderr("user-defined mapping:") + fsMap.Fprint(os.Stderr) + } handler = loggingHandler(handler) } @@ -192,7 +183,7 @@ func main() { // 1) set timestamp right away so that the indexer is kicked on fsTree.set(nil) // 2) compute initial directory tree in a goroutine so that launch is quick - go func() { fsTree.set(newDirectory(".", maxDirDepth)) }() + go func() { fsTree.set(newDirectory(goroot, maxDirDepth)) }() // Start sync goroutine, if enabled. if *syncCmd != "" && *syncMin > 0 { diff --git a/src/cmd/godoc/mapping.go b/src/cmd/godoc/mapping.go index 1143a4bdc0..ed2483d8be 100644 --- a/src/cmd/godoc/mapping.go +++ b/src/cmd/godoc/mapping.go @@ -27,13 +27,13 @@ import ( // until a valid mapping is found. For instance, for the mapping: // // user -> /home/user -// public -> /home/user/public +// public -> /home/user/public // public -> /home/build/public // // the relative paths below are mapped to absolute paths as follows: // // user/foo -> /home/user/foo -// public/net/rpc/file1.go -> /home/user/public/net/rpc/file1.go +// public/net/rpc/file1.go -> /home/user/public/net/rpc/file1.go // // If there is no /home/user/public/net/rpc/file2.go, the next public // mapping entry is used to map the relative path to: @@ -43,7 +43,8 @@ import ( // (assuming that file exists). // type Mapping struct { - list []mapping + list []mapping + prefixes []string } @@ -71,7 +72,7 @@ type mapping struct { // leads to the following mapping: // // user -> /home/user -// public -> /home/build/public +// public -> /home/build/public // func (m *Mapping) Init(paths string) { cwd, _ := os.Getwd() // ignore errors @@ -105,7 +106,7 @@ func (m *Mapping) Init(paths string) { // add mapping if it is new if i >= n { _, prefix := pathutil.Split(path) - list[i] = mapping{prefix, path} + list[n] = mapping{prefix, path} n++ } } @@ -118,6 +119,45 @@ func (m *Mapping) Init(paths string) { func (m *Mapping) IsEmpty() bool { return len(m.list) == 0 } +// PrefixList returns a list of all prefixes, with duplicates removed. +// For instance, for the mapping: +// +// user -> /home/user +// public -> /home/user/public +// public -> /home/build/public +// +// the prefix list is: +// +// user, public +// +func (m *Mapping) PrefixList() []string { + // compute the list lazily + if m.prefixes == nil { + list := make([]string, len(m.list)) + n := 0 // nuber of prefixes + + for _, e := range m.list { + // check if prefix exists already + var i int + for i = 0; i < n; i++ { + if e.prefix == list[i] { + break + } + } + + // add prefix if it is new + if i >= n { + list[n] = e.prefix + n++ + } + } + m.prefixes = list[0:n] + } + + return m.prefixes +} + + // Fprint prints the mapping. func (m *Mapping) Fprint(w io.Writer) { for _, e := range m.list {
    {.repeated section Infos} - {@|infoLine} + {@|infoLine} {.end}