1
0
mirror of https://github.com/golang/go synced 2024-11-24 22:47:58 -07:00

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
This commit is contained in:
Andrew Gerrand 2012-01-20 07:37:36 +11:00
parent 5a1322a79f
commit 8bbe5ccb71
4 changed files with 147 additions and 33 deletions

View File

@ -1,5 +1,6 @@
<!--{ <!--{
"Title": "Documentation" "Title": "Documentation",
"Path": "/doc/"
}--> }-->
<div class="left-column"> <div class="left-column">

View File

@ -1,3 +1,7 @@
<!--{
"Path": "/"
}-->
<link rel="stylesheet" type="text/css" href="/doc/frontpage.css"> <link rel="stylesheet" type="text/css" href="/doc/frontpage.css">
<script src="http://www.google.com/jsapi" type="text/javascript"></script> <script src="http://www.google.com/jsapi" type="text/javascript"></script>

View File

@ -80,6 +80,7 @@ var (
fsTree RWValue // *Directory tree of packages, updated with each sync fsTree RWValue // *Directory tree of packages, updated with each sync
pathFilter RWValue // filter used when building fsMap directory trees pathFilter RWValue // filter used when building fsMap directory trees
fsModified RWValue // timestamp of last call to invalidateIndex fsModified RWValue // timestamp of last call to invalidateIndex
docMetadata RWValue // mapping from paths to *Metadata
// http handlers // http handlers
fileServer http.Handler // default file server fileServer http.Handler // default file server
@ -698,11 +699,6 @@ var (
jsonEnd = []byte("}-->") jsonEnd = []byte("}-->")
) )
type Metadata struct {
Title string
Subtitle string
}
func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) { func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
// get HTML body contents // get HTML body contents
src, err := ReadFile(fs, abspath) 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. // if it begins with a JSON blob, read in the metadata.
var meta Metadata meta, src, err := extractMetadata(src)
if bytes.HasPrefix(src, jsonStart) { if err != nil {
if end := bytes.Index(src, jsonEnd); end > -1 { log.Printf("decoding metadata %s: %v", relpath, err)
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):]
}
} }
// if it's the language spec, add tags to EBNF productions // if it's the language spec, add tags to EBNF productions
@ -790,20 +780,21 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
} }
func serveFile(w http.ResponseWriter, r *http.Request) { func serveFile(w http.ResponseWriter, r *http.Request) {
relpath := r.URL.Path[1:] // serveFile URL paths start with '/' relpath := r.URL.Path
abspath := absolutePath(relpath, *goroot)
// pick off special cases and hand the rest to the standard file server // Check to see if we need to redirect or serve another file.
switch r.URL.Path { if m := metadataFor(relpath); m != nil {
case "/": if m.Path != relpath {
serveHTMLDoc(w, r, filepath.Join(*goroot, "doc", "root.html"), "doc/root.html") // Redirect to canonical path.
return http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
case "/doc/root.html":
// hide landing page from its real name
http.Redirect(w, r, "/", http.StatusMovedPermanently)
return return
} }
// Serve from the actual filesystem path.
relpath = m.filePath
}
relpath = relpath[1:] // strip leading slash
abspath := absolutePath(relpath, *goroot)
switch path.Ext(relpath) { switch path.Ext(relpath) {
case ".html": case ".html":
@ -1303,6 +1294,120 @@ func search(w http.ResponseWriter, r *http.Request) {
servePage(w, title, "", query, contents) servePage(w, title, "", query, contents)
} }
// ----------------------------------------------------------------------------
// 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 // Indexer
@ -1311,6 +1416,7 @@ func search(w http.ResponseWriter, r *http.Request) {
// //
func invalidateIndex() { func invalidateIndex() {
fsModified.set(nil) fsModified.set(nil)
refreshMetadata()
} }
// indexUpToDate() returns true if the search index is not older // indexUpToDate() returns true if the search index is not older

View File

@ -337,6 +337,9 @@ func main() {
}() }()
} }
// Periodically refresh metadata.
go refreshMetadataLoop()
// Initialize search index. // Initialize search index.
if *indexEnabled { if *indexEnabled {
go indexer() go indexer()