diff options
author | Andrew Gerrand <adg@golang.org> | 2012-01-20 07:37:36 +1100 |
---|---|---|
committer | Andrew Gerrand <adg@golang.org> | 2012-01-20 07:37:36 +1100 |
commit | 8bbe5ccb71b7dea0bb814decc80e7a2e53edf07d (patch) | |
tree | ed316f611a102077262308b7ba5b43ecd9c02a8c | |
parent | 5a1322a79f8370c3fdacd79f02656c20349ba7c1 (diff) | |
download | go-8bbe5ccb71b7dea0bb814decc80e7a2e53edf07d.tar.gz go-8bbe5ccb71b7dea0bb814decc80e7a2e53edf07d.zip |
godoc: support canonical Paths in HTML metadata
Redirect to the canonical path when the old path is accessed.
R=gri
CC=golang-dev
https://golang.org/cl/5536061
-rw-r--r-- | doc/docs.html | 3 | ||||
-rw-r--r-- | doc/root.html | 4 | ||||
-rw-r--r-- | src/cmd/godoc/godoc.go | 170 | ||||
-rw-r--r-- | src/cmd/godoc/main.go | 3 |
4 files changed, 147 insertions, 33 deletions
diff --git a/doc/docs.html b/doc/docs.html index c0ced98de4..e99017fca0 100644 --- a/doc/docs.html +++ b/doc/docs.html @@ -1,5 +1,6 @@ <!--{ - "Title": "Documentation" + "Title": "Documentation", + "Path": "/doc/" }--> <div class="left-column"> diff --git a/doc/root.html b/doc/root.html index 635df1a052..23a35eb844 100644 --- a/doc/root.html +++ b/doc/root.html @@ -1,3 +1,7 @@ +<!--{ + "Path": "/" +}--> + <link rel="stylesheet" type="text/css" href="/doc/frontpage.css"> <script src="http://www.google.com/jsapi" type="text/javascript"></script> diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index 61a3142ff6..06da96b0fb 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -74,12 +74,13 @@ var ( 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 + 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 // http handlers fileServer http.Handler // default file server @@ -698,11 +699,6 @@ var ( jsonEnd = []byte("}-->") ) -type Metadata struct { - Title string - Subtitle string -} - func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) { // get HTML body contents src, err := ReadFile(fs, abspath) @@ -720,15 +716,9 @@ func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin } // if it begins with a JSON blob, read in the metadata. - var meta Metadata - if bytes.HasPrefix(src, jsonStart) { - if end := bytes.Index(src, jsonEnd); end > -1 { - b := src[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing } - if err := json.Unmarshal(b, &meta); err != nil { - log.Printf("decoding metadata for %s: %v", relpath, err) - } - src = src[end+len(jsonEnd):] - } + meta, src, err := extractMetadata(src) + if err != nil { + log.Printf("decoding metadata %s: %v", relpath, err) } // if it's the language spec, add tags to EBNF productions @@ -790,21 +780,22 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str } func serveFile(w http.ResponseWriter, r *http.Request) { - relpath := r.URL.Path[1:] // serveFile URL paths start with '/' - abspath := absolutePath(relpath, *goroot) + relpath := r.URL.Path - // pick off special cases and hand the rest to the standard file server - switch r.URL.Path { - case "/": - serveHTMLDoc(w, r, filepath.Join(*goroot, "doc", "root.html"), "doc/root.html") - return - - case "/doc/root.html": - // hide landing page from its real name - http.Redirect(w, r, "/", http.StatusMovedPermanently) - return + // Check to see if we need to redirect or serve another file. + if m := metadataFor(relpath); m != nil { + if m.Path != relpath { + // Redirect to canonical path. + http.Redirect(w, r, m.Path, http.StatusMovedPermanently) + return + } + // Serve from the actual filesystem path. + relpath = m.filePath } + relpath = relpath[1:] // strip leading slash + abspath := absolutePath(relpath, *goroot) + switch path.Ext(relpath) { case ".html": if strings.HasSuffix(relpath, "/index.html") { @@ -1304,6 +1295,120 @@ func search(w http.ResponseWriter, r *http.Request) { } // ---------------------------------------------------------------------------- +// Documentation Metadata + +type Metadata struct { + Title string + Subtitle string + Path string // canonical path for this page + filePath string // filesystem path relative to goroot +} + +// extractMetadata extracts the Metadata from a byte slice. +// It returns the Metadata value and the remaining data. +// If no metadata is present the original byte slice is returned. +// +func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) { + tail = b + if !bytes.HasPrefix(b, jsonStart) { + return + } + end := bytes.Index(b, jsonEnd) + if end < 0 { + return + } + b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing } + if err = json.Unmarshal(b, &meta); err != nil { + return + } + tail = tail[end+len(jsonEnd):] + return +} + +// updateMetadata scans $GOROOT/doc for HTML files, reads their metadata, +// and updates the docMetadata map. +// +func updateMetadata() { + metadata := make(map[string]*Metadata) + var scan func(string) // scan is recursive + scan = func(dir string) { + fis, err := fs.ReadDir(dir) + if err != nil { + log.Println("updateMetadata:", err) + return + } + for _, fi := range fis { + name := filepath.Join(dir, fi.Name()) + if fi.IsDir() { + scan(name) // recurse + continue + } + if !strings.HasSuffix(name, ".html") { + continue + } + // Extract metadata from the file. + b, err := ReadFile(fs, name) + if err != nil { + log.Printf("updateMetadata %s: %v", name, err) + continue + } + meta, _, err := extractMetadata(b) + if err != nil { + log.Printf("updateMetadata: %s: %v", name, err) + continue + } + // Store relative filesystem path in Metadata. + meta.filePath = filepath.Join("/", name[len(*goroot):]) + if meta.Path == "" { + // If no Path, canonical path is actual path. + meta.Path = meta.filePath + } + // Store under both paths. + metadata[meta.Path] = &meta + metadata[meta.filePath] = &meta + } + } + scan(filepath.Join(*goroot, "doc")) + docMetadata.set(metadata) +} + +// 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. +// +var refreshMetadataSignal = make(chan bool, 1) + +// refreshMetadata sends a signal to update docMetadata. If a refresh is in +// progress the metadata will be refreshed again afterward. +// +func refreshMetadata() { + select { + case refreshMetadataSignal <- true: + default: + } +} + +// refreshMetadataLoop runs forever, updating docMetadata when the underlying +// file system changes. It should be launched in a goroutine by main. +// +func refreshMetadataLoop() { + for { + <-refreshMetadataSignal + updateMetadata() + time.Sleep(10 * time.Second) // at most once every 10 seconds + } +} + +// metadataFor returns the *Metadata for a given relative path or nil if none +// exists. +// +func metadataFor(relpath string) *Metadata { + if m, _ := docMetadata.get(); m != nil { + return m.(map[string]*Metadata)[relpath] + } + return nil +} + +// ---------------------------------------------------------------------------- // Indexer // invalidateIndex should be called whenever any of the file systems @@ -1311,6 +1416,7 @@ func search(w http.ResponseWriter, r *http.Request) { // func invalidateIndex() { fsModified.set(nil) + refreshMetadata() } // indexUpToDate() returns true if the search index is not older diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index 47369a3b4c..9f4659151e 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -337,6 +337,9 @@ func main() { }() } + // Periodically refresh metadata. + go refreshMetadataLoop() + // Initialize search index. if *indexEnabled { go indexer() |