godoc: add a Corpus and Presentation type
Start of moving globals into those types. R=golang-dev, adg CC=golang-dev https://golang.org/cl/11484043
This commit is contained in:
parent
3af65e49c2
commit
4fc6323385
|
|
@ -62,7 +62,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
|
|||
cw, err := loadCodewalk(abspath + ".xml")
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
godoc.ServeError(w, r, relpath, err)
|
||||
pres.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
godoc.ServePage(w, godoc.Page{
|
||||
pres.ServePage(w, godoc.Page{
|
||||
Title: "Codewalk: " + cw.Title,
|
||||
Tabtitle: cw.Title,
|
||||
Body: applyTemplate(codewalkHTML, "codewalk", cw),
|
||||
|
|
@ -188,7 +188,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
|
|||
dir, err := godoc.FS.ReadDir(abspath)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
godoc.ServeError(w, r, relpath, err)
|
||||
pres.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
var v []interface{}
|
||||
|
|
@ -205,7 +205,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
|
|||
}
|
||||
}
|
||||
|
||||
godoc.ServePage(w, godoc.Page{
|
||||
pres.ServePage(w, godoc.Page{
|
||||
Title: "Codewalks",
|
||||
Body: applyTemplate(codewalkdirHTML, "codewalkdir", v),
|
||||
})
|
||||
|
|
@ -222,7 +222,7 @@ func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
|
|||
data, err := vfs.ReadFile(godoc.FS, abspath)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
godoc.ServeError(w, r, f, err)
|
||||
pres.ServeError(w, r, f, err)
|
||||
return
|
||||
}
|
||||
lo, _ := strconv.Atoi(r.FormValue("lo"))
|
||||
|
|
|
|||
|
|
@ -9,16 +9,12 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
htmlpkg "html"
|
||||
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
pathpkg "path"
|
||||
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
|
|
@ -55,8 +51,7 @@ 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)")
|
||||
goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
|
||||
|
||||
// layout control
|
||||
_ = flagInt(&godoc.TabWidth, "tabwidth", 4, "tab width")
|
||||
|
|
@ -67,8 +62,8 @@ var (
|
|||
_ = flagBool(&godoc.DeclLinks, "links", true, "link identifiers to their declarations")
|
||||
|
||||
// search index
|
||||
_ = flagBool(&godoc.IndexEnabled, "index", false, "enable search index")
|
||||
_ = flagString(&godoc.IndexFiles, "index_files", "", "glob pattern specifying index files;"+
|
||||
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")
|
||||
_ = flagInt(&godoc.MaxResults, "maxresults", 10000, "maximum number of full text search results shown")
|
||||
_ = flagFloat64(&godoc.IndexThrottle, "index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
|
||||
|
|
@ -77,6 +72,8 @@ var (
|
|||
_ = flagString(&godoc.NotesRx, "notes", "BUG", "regular expression matching note markers to show")
|
||||
)
|
||||
|
||||
var pres *godoc.Presentation
|
||||
|
||||
func registerPublicHandlers(mux *http.ServeMux) {
|
||||
godoc.CmdHandler.RegisterWithMux(mux)
|
||||
godoc.PkgHandler.RegisterWithMux(mux)
|
||||
|
|
@ -88,16 +85,6 @@ func registerPublicHandlers(mux *http.ServeMux) {
|
|||
mux.HandleFunc("/", serveFile)
|
||||
}
|
||||
|
||||
func initFSTree() {
|
||||
dir := godoc.NewDirectory(pathpkg.Join("/", *testDir), -1)
|
||||
if dir == nil {
|
||||
log.Println("Warning: FSTree is nil")
|
||||
return
|
||||
}
|
||||
godoc.FSTree.Set(dir)
|
||||
godoc.InvalidateIndex()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Templates
|
||||
|
||||
|
|
@ -176,12 +163,12 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit
|
|||
src, err := vfs.ReadFile(godoc.FS, abspath)
|
||||
if err != nil {
|
||||
log.Printf("ReadFile: %s", err)
|
||||
godoc.ServeError(w, r, relpath, err)
|
||||
pres.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if r.FormValue("m") == "text" {
|
||||
godoc.ServeText(w, src)
|
||||
pres.ServeText(w, src)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +178,7 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit
|
|||
buf.WriteString("</pre>")
|
||||
fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
|
||||
|
||||
godoc.ServePage(w, godoc.Page{
|
||||
pres.ServePage(w, godoc.Page{
|
||||
Title: title + " " + relpath,
|
||||
Tabtitle: relpath,
|
||||
Body: buf.Bytes(),
|
||||
|
|
@ -205,11 +192,11 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
|
|||
|
||||
list, err := godoc.FS.ReadDir(abspath)
|
||||
if err != nil {
|
||||
godoc.ServeError(w, r, relpath, err)
|
||||
pres.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
godoc.ServePage(w, godoc.Page{
|
||||
pres.ServePage(w, godoc.Page{
|
||||
Title: "Directory " + relpath,
|
||||
Tabtitle: relpath,
|
||||
Body: applyTemplate(godoc.DirlistHTML, "dirlistHTML", list),
|
||||
|
|
@ -241,7 +228,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
godoc.ServeHTMLDoc(w, r, abspath, relpath)
|
||||
pres.ServeHTMLDoc(w, r, abspath, relpath)
|
||||
return
|
||||
|
||||
case ".go":
|
||||
|
|
@ -252,7 +239,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
|
|||
dir, err := godoc.FS.Lstat(abspath)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
godoc.ServeError(w, r, relpath, err)
|
||||
pres.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +248,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(godoc.FS, index) {
|
||||
godoc.ServeHTMLDoc(w, r, index, index)
|
||||
pres.ServeHTMLDoc(w, r, index, index)
|
||||
return
|
||||
}
|
||||
serveDirectory(w, r, abspath, relpath)
|
||||
|
|
@ -362,7 +349,7 @@ func lookup(query string) (result SearchResult) {
|
|||
}
|
||||
|
||||
// is the result accurate?
|
||||
if godoc.IndexEnabled {
|
||||
if pres.Corpus.IndexEnabled {
|
||||
if _, ts := godoc.FSModified.Get(); timestamp.Before(ts) {
|
||||
// The index is older than the latest file system change under godoc's observation.
|
||||
result.Alert = "Indexing in progress: result may be inaccurate"
|
||||
|
|
@ -379,7 +366,7 @@ func search(w http.ResponseWriter, r *http.Request) {
|
|||
result := lookup(query)
|
||||
|
||||
if godoc.GetPageInfoMode(r)&godoc.NoHTML != 0 {
|
||||
godoc.ServeText(w, applyTemplate(godoc.SearchText, "searchText", result))
|
||||
pres.ServeText(w, applyTemplate(godoc.SearchText, "searchText", result))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -390,7 +377,7 @@ func search(w http.ResponseWriter, r *http.Request) {
|
|||
title = fmt.Sprintf(`No results found for query %q`, query)
|
||||
}
|
||||
|
||||
godoc.ServePage(w, godoc.Page{
|
||||
pres.ServePage(w, godoc.Page{
|
||||
Title: title,
|
||||
Tabtitle: query,
|
||||
Query: query,
|
||||
|
|
|
|||
|
|
@ -158,9 +158,8 @@ func main() {
|
|||
usage()
|
||||
}
|
||||
|
||||
if godoc.TabWidth < 0 {
|
||||
log.Fatalf("negative tabwidth %d", godoc.TabWidth)
|
||||
}
|
||||
fs := vfs.NameSpace{}
|
||||
godoc.FS = fs // temp hack
|
||||
|
||||
// Determine file system to use.
|
||||
// TODO(gri) - fs and fsHttp should really be the same. Try to unify.
|
||||
|
|
@ -168,9 +167,9 @@ func main() {
|
|||
// same is true for the http handlers in initHandlers.
|
||||
if *zipfile == "" {
|
||||
// use file system of underlying OS
|
||||
godoc.FS.Bind("/", vfs.OS(*goroot), "/", vfs.BindReplace)
|
||||
fs.Bind("/", vfs.OS(*goroot), "/", vfs.BindReplace)
|
||||
if *templateDir != "" {
|
||||
godoc.FS.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
|
||||
fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
|
||||
}
|
||||
} else {
|
||||
// use file system specified via .zip file (path separator must be '/')
|
||||
|
|
@ -179,16 +178,23 @@ func main() {
|
|||
log.Fatalf("%s: %s\n", *zipfile, err)
|
||||
}
|
||||
defer rc.Close() // be nice (e.g., -writeIndex mode)
|
||||
godoc.FS.Bind("/", zipvfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
|
||||
fs.Bind("/", zipvfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
|
||||
}
|
||||
|
||||
// Bind $GOPATH trees into Go root.
|
||||
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
||||
godoc.FS.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
|
||||
fs.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
|
||||
}
|
||||
|
||||
readTemplates()
|
||||
godoc.InitHandlers(godoc.FS)
|
||||
|
||||
corpus := godoc.NewCorpus(fs)
|
||||
corpus.IndexEnabled = *indexEnabled
|
||||
corpus.IndexFiles = *indexFiles
|
||||
|
||||
pres = godoc.NewPresentation(corpus)
|
||||
// ...
|
||||
godoc.InitHandlers(pres)
|
||||
|
||||
if *writeIndex {
|
||||
// Write search index and exit.
|
||||
|
|
@ -198,7 +204,6 @@ func main() {
|
|||
|
||||
log.Println("initialize file systems")
|
||||
*verbose = true // want to see what happens
|
||||
initFSTree()
|
||||
|
||||
godoc.IndexThrottle = 1.0
|
||||
godoc.UpdateIndex()
|
||||
|
|
@ -221,8 +226,7 @@ func main() {
|
|||
// Print content that would be served at the URL *urlFlag.
|
||||
if *urlFlag != "" {
|
||||
registerPublicHandlers(http.DefaultServeMux)
|
||||
initFSTree()
|
||||
godoc.UpdateMetadata()
|
||||
|
||||
// Try up to 10 fetches, following redirects.
|
||||
urlstr := *urlFlag
|
||||
for i := 0; i < 10; i++ {
|
||||
|
|
@ -268,30 +272,21 @@ func main() {
|
|||
log.Printf("goroot = %s", *goroot)
|
||||
log.Printf("tabwidth = %d", godoc.TabWidth)
|
||||
switch {
|
||||
case !godoc.IndexEnabled:
|
||||
case !*indexEnabled:
|
||||
log.Print("search index disabled")
|
||||
case godoc.MaxResults > 0:
|
||||
log.Printf("full text index enabled (maxresults = %d)", godoc.MaxResults)
|
||||
default:
|
||||
log.Print("identifier search index enabled")
|
||||
}
|
||||
godoc.FS.Fprint(os.Stderr)
|
||||
fs.Fprint(os.Stderr)
|
||||
handler = loggingHandler(handler)
|
||||
}
|
||||
|
||||
registerPublicHandlers(http.DefaultServeMux)
|
||||
|
||||
// Initialize default directory tree with corresponding timestamp.
|
||||
// (Do it in a goroutine so that launch is quick.)
|
||||
go initFSTree()
|
||||
|
||||
// Immediately update metadata.
|
||||
godoc.UpdateMetadata()
|
||||
// Periodically refresh metadata.
|
||||
go godoc.RefreshMetadataLoop()
|
||||
|
||||
// Initialize search index.
|
||||
if godoc.IndexEnabled {
|
||||
if *indexEnabled {
|
||||
go godoc.RunIndexer()
|
||||
}
|
||||
|
||||
|
|
@ -335,18 +330,18 @@ func main() {
|
|||
var forceCmd bool
|
||||
var abspath, relpath string
|
||||
if filepath.IsAbs(path) {
|
||||
godoc.FS.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
|
||||
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
|
||||
abspath = target
|
||||
} else if build.IsLocalImport(path) {
|
||||
cwd, _ := os.Getwd() // ignore errors
|
||||
path = filepath.Join(cwd, path)
|
||||
godoc.FS.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
|
||||
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
|
||||
abspath = target
|
||||
} else if strings.HasPrefix(path, cmdPrefix) {
|
||||
path = strings.TrimPrefix(path, cmdPrefix)
|
||||
forceCmd = true
|
||||
} else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
|
||||
godoc.FS.Bind(target, vfs.OS(bp.Dir), "/", vfs.BindReplace)
|
||||
fs.Bind(target, vfs.OS(bp.Dir), "/", vfs.BindReplace)
|
||||
abspath = target
|
||||
relpath = bp.ImportPath
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package godoc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
pathpkg "path"
|
||||
|
||||
"code.google.com/p/go.tools/godoc/util"
|
||||
"code.google.com/p/go.tools/godoc/vfs"
|
||||
)
|
||||
|
||||
// A Corpus holds all the state related to serving and indexing a
|
||||
// collection of Go code.
|
||||
//
|
||||
// Construct a new Corpus with NewCorpus, then modify options,
|
||||
// then call its Init method.
|
||||
type Corpus struct {
|
||||
fs vfs.FileSystem
|
||||
|
||||
// IndexEnabled controls whether indexing is enabled.
|
||||
IndexEnabled bool
|
||||
|
||||
// IndexFiles specifies a glob pattern specifying index files.
|
||||
// If not empty, the index is read from these files in sorted
|
||||
// order.
|
||||
IndexFiles string
|
||||
|
||||
IndexThrottle float64
|
||||
|
||||
// MaxResults optionally specifies the maximum results for indexing.
|
||||
// The default is 1000.
|
||||
MaxResults int
|
||||
|
||||
testDir string // TODO(bradfitz,adg): migrate old godoc flag? looks unused.
|
||||
|
||||
// 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.
|
||||
refreshMetadataSignal chan bool
|
||||
|
||||
// file system information
|
||||
fsTree util.RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
|
||||
fsModified util.RWValue // timestamp of last call to invalidateIndex
|
||||
docMetadata util.RWValue // mapping from paths to *Metadata
|
||||
|
||||
}
|
||||
|
||||
// NewCorpus returns a new Corpus from a filesystem.
|
||||
// Set any options on Corpus before calling the Corpus.Init method.
|
||||
func NewCorpus(fs vfs.FileSystem) *Corpus {
|
||||
c := &Corpus{
|
||||
fs: fs,
|
||||
refreshMetadataSignal: make(chan bool, 1),
|
||||
|
||||
MaxResults: 1000,
|
||||
IndexEnabled: true,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Init initializes Corpus, once options on Corpus are set.
|
||||
// It must be called before any subsequent method calls.
|
||||
func (c *Corpus) Init() error {
|
||||
// TODO(bradfitz): do this in a goroutine because newDirectory might block for a long time?
|
||||
// It used to be sometimes done in a goroutine before, at least in HTTP server mode.
|
||||
if err := c.initFSTree(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.updateMetadata()
|
||||
go c.refreshMetadataLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Corpus) initFSTree() error {
|
||||
dir := c.newDirectory(pathpkg.Join("/", c.testDir), -1)
|
||||
if dir == nil {
|
||||
return errors.New("godoc: corpus fstree is nil")
|
||||
}
|
||||
c.fsTree.Set(dir)
|
||||
c.invalidateIndex()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ func isPkgDir(fi os.FileInfo) bool {
|
|||
}
|
||||
|
||||
type treeBuilder struct {
|
||||
c *Corpus
|
||||
maxDepth int
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +70,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
|
|||
}
|
||||
}
|
||||
|
||||
list, _ := FS.ReadDir(path)
|
||||
list, _ := b.c.fs.ReadDir(path)
|
||||
|
||||
// determine number of subdirectories and if there are package files
|
||||
ndirs := 0
|
||||
|
|
@ -161,9 +162,9 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
|
|||
// are assumed to contain package files even if their contents are not known
|
||||
// (i.e., in this case the tree may contain directories w/o any package files).
|
||||
//
|
||||
func NewDirectory(root string, maxDepth int) *Directory {
|
||||
func (c *Corpus) newDirectory(root string, maxDepth int) *Directory {
|
||||
// The root could be a symbolic link so use Stat not Lstat.
|
||||
d, err := FS.Stat(root)
|
||||
d, err := c.fs.Stat(root)
|
||||
// If we fail here, report detailed error messages; otherwise
|
||||
// is is hard to see why a directory tree was not built.
|
||||
switch {
|
||||
|
|
@ -177,7 +178,7 @@ func NewDirectory(root string, maxDepth int) *Directory {
|
|||
if maxDepth < 0 {
|
||||
maxDepth = 1e6 // "infinity"
|
||||
}
|
||||
b := treeBuilder{maxDepth}
|
||||
b := treeBuilder{c, maxDepth}
|
||||
// the file set provided is only for local parsing, no position
|
||||
// information escapes and thus we don't need to save the set
|
||||
return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
// holds the source tree, and so on. This means that the URLs served by
|
||||
// the godoc server are the same as the paths in the virtual file
|
||||
// system, which helps keep things simple.
|
||||
// TODO(bradfitz): delete this global
|
||||
var FS = vfs.NameSpace{}
|
||||
|
||||
// Old flags
|
||||
|
|
@ -54,8 +55,6 @@ var (
|
|||
// TODO(bradfitz,adg): delete this flag
|
||||
ShowPlayground = false
|
||||
|
||||
IndexEnabled = false
|
||||
|
||||
ShowTimestamps = false
|
||||
|
||||
Verbose = false
|
||||
|
|
|
|||
|
|
@ -1038,9 +1038,9 @@ func (x *Index) LookupRegexp(r *regexp.Regexp, n int) (found int, result []FileL
|
|||
|
||||
// InvalidateIndex should be called whenever any of the file systems
|
||||
// under godoc's observation change so that the indexer is kicked on.
|
||||
func InvalidateIndex() {
|
||||
FSModified.Set(nil)
|
||||
refreshMetadata()
|
||||
func (c *Corpus) invalidateIndex() {
|
||||
c.fsModified.Set(nil)
|
||||
c.refreshMetadata()
|
||||
}
|
||||
|
||||
// indexUpToDate() returns true if the search index is not older
|
||||
|
|
|
|||
|
|
@ -58,11 +58,11 @@ func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) {
|
|||
|
||||
// UpdateMetadata scans $GOROOT/doc for HTML files, reads their metadata,
|
||||
// and updates the DocMetadata map.
|
||||
func UpdateMetadata() {
|
||||
func (c *Corpus) updateMetadata() {
|
||||
metadata := make(map[string]*Metadata)
|
||||
var scan func(string) // scan is recursive
|
||||
scan = func(dir string) {
|
||||
fis, err := FS.ReadDir(dir)
|
||||
fis, err := c.fs.ReadDir(dir)
|
||||
if err != nil {
|
||||
log.Println("updateMetadata:", err)
|
||||
return
|
||||
|
|
@ -77,7 +77,7 @@ func UpdateMetadata() {
|
|||
continue
|
||||
}
|
||||
// Extract metadata from the file.
|
||||
b, err := vfs.ReadFile(FS, name)
|
||||
b, err := vfs.ReadFile(c.fs, name)
|
||||
if err != nil {
|
||||
log.Printf("updateMetadata %s: %v", name, err)
|
||||
continue
|
||||
|
|
@ -123,27 +123,22 @@ func MetadataFor(relpath string) *Metadata {
|
|||
return nil
|
||||
}
|
||||
|
||||
// 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() {
|
||||
func (c *Corpus) refreshMetadata() {
|
||||
select {
|
||||
case refreshMetadataSignal <- true:
|
||||
case c.refreshMetadataSignal <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// RefreshMetadataLoop runs forever, updating DocMetadata when the underlying
|
||||
// file system changes. It should be launched in a goroutine.
|
||||
func RefreshMetadataLoop() {
|
||||
func (c *Corpus) refreshMetadataLoop() {
|
||||
for {
|
||||
<-refreshMetadataSignal
|
||||
UpdateMetadata()
|
||||
<-c.refreshMetadataSignal
|
||||
c.updateMetadata()
|
||||
time.Sleep(10 * time.Second) // at most once every 10 seconds
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ var (
|
|||
SearchDescXML *template.Template
|
||||
)
|
||||
|
||||
func ServePage(w http.ResponseWriter, page Page) {
|
||||
func (p *Presentation) ServePage(w http.ResponseWriter, page Page) {
|
||||
if page.Tabtitle == "" {
|
||||
page.Tabtitle = page.Title
|
||||
}
|
||||
page.SearchBox = IndexEnabled
|
||||
page.SearchBox = p.Corpus.IndexEnabled
|
||||
page.Playground = ShowPlayground
|
||||
page.Version = runtime.Version()
|
||||
if err := GodocHTML.Execute(w, page); err != nil && err != http.ErrBodyNotAllowed {
|
||||
|
|
@ -51,9 +51,9 @@ func ServePage(w http.ResponseWriter, page Page) {
|
|||
}
|
||||
}
|
||||
|
||||
func ServeError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
|
||||
func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
ServePage(w, Page{
|
||||
p.ServePage(w, Page{
|
||||
Title: "File " + relpath,
|
||||
Subtitle: relpath,
|
||||
Body: applyTemplate(ErrorHTML, "errorHTML", err), // err may contain an absolute path!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package godoc
|
||||
|
||||
import (
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Presentation generates output from a corpus.
|
||||
type Presentation struct {
|
||||
Corpus *Corpus
|
||||
|
||||
// TabWidth optionally specifies the tab width.
|
||||
TabWidth int
|
||||
|
||||
ShowTimestamps bool
|
||||
ShowPlayground bool
|
||||
ShowExamples bool
|
||||
DeclLinks bool
|
||||
}
|
||||
|
||||
// NewPresentation returns a new Presentation from a corpus.
|
||||
func NewPresentation(c *Corpus) *Presentation {
|
||||
if c == nil {
|
||||
panic("nil Corpus")
|
||||
}
|
||||
return &Presentation{
|
||||
Corpus: c,
|
||||
TabWidth: 4,
|
||||
ShowExamples: true,
|
||||
DeclLinks: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Presentation) FuncMap() template.FuncMap {
|
||||
panic("")
|
||||
}
|
||||
|
|
@ -42,16 +42,19 @@ var (
|
|||
DocMetadata util.RWValue // mapping from paths to *Metadata
|
||||
)
|
||||
|
||||
func InitHandlers(fs vfs.FileSystem) {
|
||||
FileServer = http.FileServer(httpfs.New(fs))
|
||||
CmdHandler = Server{"/cmd/", "/src/cmd"}
|
||||
PkgHandler = Server{"/pkg/", "/src/pkg"}
|
||||
func InitHandlers(p *Presentation) {
|
||||
c := p.Corpus
|
||||
FileServer = http.FileServer(httpfs.New(c.fs))
|
||||
CmdHandler = Server{p, c, "/cmd/", "/src/cmd"}
|
||||
PkgHandler = Server{p, c, "/pkg/", "/src/pkg"}
|
||||
}
|
||||
|
||||
// Server is a godoc server.
|
||||
type Server struct {
|
||||
pattern string // url pattern; e.g. "/pkg/"
|
||||
fsRoot string // file system root to which the pattern is mapped
|
||||
p *Presentation
|
||||
c *Corpus // copy of p.Corpus
|
||||
pattern string // url pattern; e.g. "/pkg/"
|
||||
fsRoot string // file system root to which the pattern is mapped
|
||||
}
|
||||
|
||||
func (s *Server) FSRoot() string { return s.fsRoot }
|
||||
|
|
@ -178,7 +181,7 @@ func (h *Server) GetPageInfo(abspath, relpath string, mode PageInfoMode) *PageIn
|
|||
// 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, 1)
|
||||
dir = h.c.newDirectory(abspath, 1)
|
||||
timestamp = time.Now()
|
||||
}
|
||||
info.Dirs = dir.listing(true)
|
||||
|
|
@ -202,12 +205,12 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
info := h.GetPageInfo(abspath, relpath, mode)
|
||||
if info.Err != nil {
|
||||
log.Print(info.Err)
|
||||
ServeError(w, r, relpath, info.Err)
|
||||
h.p.ServeError(w, r, relpath, info.Err)
|
||||
return
|
||||
}
|
||||
|
||||
if mode&NoHTML != 0 {
|
||||
ServeText(w, applyTemplate(PackageText, "packageText", info))
|
||||
h.p.ServeText(w, applyTemplate(PackageText, "packageText", info))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -243,7 +246,7 @@ func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
tabtitle = "Commands"
|
||||
}
|
||||
|
||||
ServePage(w, Page{
|
||||
h.p.ServePage(w, Page{
|
||||
Title: title,
|
||||
Tabtitle: tabtitle,
|
||||
Subtitle: subtitle,
|
||||
|
|
@ -431,16 +434,16 @@ func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
|
|||
return
|
||||
}
|
||||
|
||||
func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
|
||||
src, err := vfs.ReadFile(FS, abspath)
|
||||
func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
|
||||
src, err := vfs.ReadFile(p.Corpus.fs, abspath)
|
||||
if err != nil {
|
||||
log.Printf("ReadFile: %s", err)
|
||||
ServeError(w, r, relpath, err)
|
||||
p.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if r.FormValue("m") == "text" {
|
||||
ServeText(w, src)
|
||||
p.ServeText(w, src)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -450,37 +453,37 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit
|
|||
buf.WriteString("</pre>")
|
||||
fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
|
||||
|
||||
ServePage(w, Page{
|
||||
p.ServePage(w, Page{
|
||||
Title: title + " " + relpath,
|
||||
Tabtitle: relpath,
|
||||
Body: buf.Bytes(),
|
||||
})
|
||||
}
|
||||
|
||||
func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
||||
func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
||||
if redirect(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
list, err := FS.ReadDir(abspath)
|
||||
if err != nil {
|
||||
ServeError(w, r, relpath, err)
|
||||
p.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
ServePage(w, Page{
|
||||
p.ServePage(w, Page{
|
||||
Title: "Directory " + relpath,
|
||||
Tabtitle: relpath,
|
||||
Body: applyTemplate(DirlistHTML, "dirlistHTML", list),
|
||||
})
|
||||
}
|
||||
|
||||
func ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
||||
func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
|
||||
// get HTML body contents
|
||||
src, err := vfs.ReadFile(FS, abspath)
|
||||
if err != nil {
|
||||
log.Printf("ReadFile: %s", err)
|
||||
ServeError(w, r, relpath, err)
|
||||
p.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -502,13 +505,13 @@ func ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin
|
|||
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)
|
||||
p.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)
|
||||
p.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
src = buf.Bytes()
|
||||
|
|
@ -521,14 +524,14 @@ func ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin
|
|||
src = buf.Bytes()
|
||||
}
|
||||
|
||||
ServePage(w, Page{
|
||||
p.ServePage(w, Page{
|
||||
Title: meta.Title,
|
||||
Subtitle: meta.Subtitle,
|
||||
Body: src,
|
||||
})
|
||||
}
|
||||
|
||||
func serveFile(w http.ResponseWriter, r *http.Request) {
|
||||
func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
|
||||
relpath := r.URL.Path
|
||||
|
||||
// Check to see if we need to redirect or serve another file.
|
||||
|
|
@ -553,18 +556,18 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
ServeHTMLDoc(w, r, abspath, relpath)
|
||||
p.ServeHTMLDoc(w, r, abspath, relpath)
|
||||
return
|
||||
|
||||
case ".go":
|
||||
serveTextFile(w, r, abspath, relpath, "Source file")
|
||||
p.serveTextFile(w, r, abspath, relpath, "Source file")
|
||||
return
|
||||
}
|
||||
|
||||
dir, err := FS.Lstat(abspath)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
ServeError(w, r, relpath, err)
|
||||
p.ServeError(w, r, relpath, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -573,10 +576,10 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(FS, index) {
|
||||
ServeHTMLDoc(w, r, index, index)
|
||||
p.ServeHTMLDoc(w, r, index, index)
|
||||
return
|
||||
}
|
||||
serveDirectory(w, r, abspath, relpath)
|
||||
p.serveDirectory(w, r, abspath, relpath)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -584,7 +587,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
|
|||
if redirectFile(w, r) {
|
||||
return
|
||||
}
|
||||
serveTextFile(w, r, abspath, relpath, "Text file")
|
||||
p.serveTextFile(w, r, abspath, relpath, "Text file")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -603,7 +606,7 @@ func serveSearchDesc(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func ServeText(w http.ResponseWriter, text []byte) {
|
||||
func (p *Presentation) ServeText(w http.ResponseWriter, text []byte) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write(text)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue