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 @}
-
+
{Decl|html}
{Doc|html-comment}
{.end}
{.end}
{.section Types}
{.repeated section @}
-
+
{Doc|html-comment}
{Decl|html}
{.repeated section Consts}
@@ -57,12 +57,12 @@
{Decl|html}
{.end}
{.repeated section Factories}
-
+
{Decl|html}
{Doc|html-comment}
{.end}
{.repeated section Methods}
-
+
{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 @}
-
+
{.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 @}
-
+
{.repeated section Files}
- {File.Path|html}
+ {File.Path|html}
{.repeated section Groups}
@@ -47,7 +47,7 @@
|
{.repeated section Infos}
- {@|infoLine}
+ {@|infoLine}
{.end}
|
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 {