diff --git a/cmd/godoc/filesystem.go b/cmd/godoc/filesystem.go index 0309d7cabe..6679380180 100644 --- a/cmd/godoc/filesystem.go +++ b/cmd/godoc/filesystem.go @@ -19,6 +19,8 @@ import ( "sort" "strings" "time" + + "code.google.com/p/go.tools/godoc/vfs" ) // fs is the file system that godoc reads from and serves. @@ -51,19 +53,13 @@ const debugNS = false // The FileSystem interface specifies the methods godoc is using // to access the file system for which it serves documentation. type FileSystem interface { - Open(path string) (readSeekCloser, error) + Open(path string) (vfs.ReadSeekCloser, error) Lstat(path string) (os.FileInfo, error) Stat(path string) (os.FileInfo, error) ReadDir(path string) ([]os.FileInfo, error) String() string } -type readSeekCloser interface { - io.Reader - io.Seeker - io.Closer -} - // ReadFile reads the file named by path from fs and returns the contents. func ReadFile(fs FileSystem, path string) ([]byte, error) { rc, err := fs.Open(path) @@ -97,7 +93,7 @@ func (root osFS) resolve(path string) string { return filepath.Join(string(root), path) } -func (root osFS) Open(path string) (readSeekCloser, error) { +func (root osFS) Open(path string) (vfs.ReadSeekCloser, error) { f, err := os.Open(root.resolve(path)) if err != nil { return nil, err @@ -319,7 +315,7 @@ func (ns nameSpace) resolve(path string) []mountedFS { } // Open implements the FileSystem Open method. -func (ns nameSpace) Open(path string) (readSeekCloser, error) { +func (ns nameSpace) Open(path string) (vfs.ReadSeekCloser, error) { var err error for _, m := range ns.resolve(path) { if debugNS { @@ -552,7 +548,7 @@ func (h *httpDir) Readdir(count int) ([]os.FileInfo, error) { // httpFile implements http.File for a file (not directory) in a FileSystem. type httpFile struct { fs FileSystem - readSeekCloser + vfs.ReadSeekCloser name string } diff --git a/cmd/godoc/godoc.go b/cmd/godoc/godoc.go index 79d485b93d..84c8fa8f1b 100644 --- a/cmd/godoc/godoc.go +++ b/cmd/godoc/godoc.go @@ -32,26 +32,13 @@ import ( "time" "unicode" "unicode/utf8" + + "code.google.com/p/go.tools/godoc/util" ) // ---------------------------------------------------------------------------- // Globals -type delayTime struct { - RWValue -} - -func (dt *delayTime) backoff(max time.Duration) { - dt.mutex.Lock() - v := dt.value.(time.Duration) * 2 - if v > max { - v = max - } - dt.value = v - // don't change dt.timestamp - calling backoff indicates an error condition - dt.mutex.Unlock() -} - var ( verbose = flag.Bool("v", false, "verbose mode") @@ -76,9 +63,9 @@ var ( indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle") // file system information - fsTree RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now) - fsModified RWValue // timestamp of last call to invalidateIndex - docMetadata RWValue // mapping from paths to *Metadata + 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 // http handlers fileServer http.Handler // default file server @@ -112,7 +99,7 @@ func initFSTree() { log.Println("Warning: FSTree is nil") return } - fsTree.set(dir) + fsTree.Set(dir) invalidateIndex() } @@ -250,7 +237,7 @@ func infoKind_htmlFunc(info SpotInfo) string { func infoLineFunc(info SpotInfo) int { line := info.Lori() if info.IsIndex() { - index, _ := searchIndex.get() + index, _ := searchIndex.Get() if index != nil { line = index.(*Index).Snippet(line).Line } else { @@ -266,7 +253,7 @@ func infoLineFunc(info SpotInfo) int { func infoSnippet_htmlFunc(info SpotInfo) string { if info.IsIndex() { - index, _ := searchIndex.get() + index, _ := searchIndex.Get() // Snippet.Text was HTML-escaped when it was generated return index.(*Index).Snippet(info.Lori()).Text } @@ -838,7 +825,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) { if redirect(w, r) { return } - if index := pathpkg.Join(abspath, "index.html"); isTextFile(index) { + if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(fs, index) { serveHTMLDoc(w, r, index, index) return } @@ -846,7 +833,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) { return } - if isTextFile(abspath) { + if util.IsTextFile(fs, abspath) { if redirectFile(w, r) { return } @@ -1168,7 +1155,7 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) *Pag // get directory information, if any var dir *Directory var timestamp time.Time - if tree, ts := fsTree.get(); tree != nil && tree.(*Directory) != nil { + if tree, ts := fsTree.Get(); tree != nil && tree.(*Directory) != 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 been computed) @@ -1256,7 +1243,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { // ---------------------------------------------------------------------------- // Search -var searchIndex RWValue +var searchIndex util.RWValue type SearchResult struct { Query string @@ -1276,7 +1263,7 @@ type SearchResult struct { func lookup(query string) (result SearchResult) { result.Query = query - index, timestamp := searchIndex.get() + index, timestamp := searchIndex.Get() if index != nil { index := index.(*Index) @@ -1311,7 +1298,7 @@ func lookup(query string) (result SearchResult) { // is the result accurate? if *indexEnabled { - if _, ts := fsModified.get(); timestamp.Before(ts) { + if _, ts := 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" } @@ -1422,7 +1409,7 @@ func updateMetadata() { } } scan("/doc") - docMetadata.set(metadata) + docMetadata.Set(metadata) } // Send a value on this channel to trigger a metadata refresh. @@ -1455,7 +1442,7 @@ func refreshMetadataLoop() { // exists. // func metadataFor(relpath string) *Metadata { - if m, _ := docMetadata.get(); m != nil { + if m, _ := docMetadata.Get(); m != nil { meta := m.(map[string]*Metadata) // If metadata for this relpath exists, return it. if p := meta[relpath]; p != nil { @@ -1479,7 +1466,7 @@ func metadataFor(relpath string) *Metadata { // under godoc's observation change so that the indexer is kicked on. // func invalidateIndex() { - fsModified.set(nil) + fsModified.Set(nil) refreshMetadata() } @@ -1487,16 +1474,16 @@ func invalidateIndex() { // than any of the file systems under godoc's observation. // func indexUpToDate() bool { - _, fsTime := fsModified.get() - _, siTime := searchIndex.get() + _, fsTime := fsModified.Get() + _, siTime := searchIndex.Get() return !fsTime.After(siTime) } // feedDirnames feeds the directory names of all directories // under the file system given by root to channel c. // -func feedDirnames(root *RWValue, c chan<- string) { - if dir, _ := root.get(); dir != nil { +func feedDirnames(root *util.RWValue, c chan<- string) { + if dir, _ := root.Get(); dir != nil { for d := range dir.(*Directory).iter(false) { c <- d.Path } @@ -1536,7 +1523,7 @@ func readIndex(filenames string) error { if err := x.Read(io.MultiReader(files...)); err != nil { return err } - searchIndex.set(x) + searchIndex.Set(x) return nil } @@ -1547,7 +1534,7 @@ func updateIndex() { start := time.Now() index := NewIndex(fsDirnames(), *maxResults > 0, *indexThrottle) stop := time.Now() - searchIndex.set(index) + searchIndex.Set(index) if *verbose { secs := stop.Sub(start).Seconds() stats := index.Stats() diff --git a/cmd/godoc/index.go b/cmd/godoc/index.go index d1292d5053..c3cfe69adf 100644 --- a/cmd/godoc/index.go +++ b/cmd/godoc/index.go @@ -54,6 +54,8 @@ import ( "strings" "time" "unicode" + + "code.google.com/p/go.tools/godoc/util" ) // ---------------------------------------------------------------------------- @@ -634,7 +636,7 @@ func (x *Indexer) addFile(filename string, goFile bool) (file *token.File, ast * return } - if isText(src) { + if util.IsText(src) { // only add the file to the file set (for the full text index) file = x.fset.AddFile(filename, x.fset.Base(), len(src)) file.SetLinesForContent(src) diff --git a/cmd/godoc/main.go b/cmd/godoc/main.go index 81e739d20c..0d1546b11f 100644 --- a/cmd/godoc/main.go +++ b/cmd/godoc/main.go @@ -212,7 +212,7 @@ func main() { if err != nil { log.Fatal(err) } - index, _ := searchIndex.get() + index, _ := searchIndex.Get() err = index.(*Index).Write(f) if err != nil { log.Fatal(err) diff --git a/cmd/godoc/zip.go b/cmd/godoc/zip.go index 620eb4f3cc..899f6914d6 100644 --- a/cmd/godoc/zip.go +++ b/cmd/godoc/zip.go @@ -27,6 +27,8 @@ import ( "sort" "strings" "time" + + "code.google.com/p/go.tools/godoc/vfs" ) // zipFI is the zip-file based implementation of FileInfo @@ -107,7 +109,7 @@ func (fs *zipFS) stat(abspath string) (int, zipFI, error) { return i, zipFI{name, file}, nil } -func (fs *zipFS) Open(abspath string) (readSeekCloser, error) { +func (fs *zipFS) Open(abspath string) (vfs.ReadSeekCloser, error) { _, fi, err := fs.stat(zipPath(abspath)) if err != nil { return nil, err diff --git a/cmd/godoc/utils.go b/godoc/util/util.go similarity index 73% rename from cmd/godoc/utils.go rename to godoc/util/util.go index 0cdb7ff7af..b3eac39a46 100644 --- a/cmd/godoc/utils.go +++ b/godoc/util/util.go @@ -1,44 +1,44 @@ -// Copyright 2010 The Go Authors. All rights reserved. +// 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. -// This file contains support functionality for godoc. - -package main +// Package util contains utility types and functions for godoc. +package util import ( + "io" pathpkg "path" "sync" "time" "unicode/utf8" + + "code.google.com/p/go.tools/godoc/vfs" ) // An RWValue wraps a value and permits mutually exclusive // access to it and records the time the value was last set. -// type RWValue struct { mutex sync.RWMutex value interface{} timestamp time.Time // time of last set() } -func (v *RWValue) set(value interface{}) { +func (v *RWValue) Set(value interface{}) { v.mutex.Lock() v.value = value v.timestamp = time.Now() v.mutex.Unlock() } -func (v *RWValue) get() (interface{}, time.Time) { +func (v *RWValue) Get() (interface{}, time.Time) { v.mutex.RLock() defer v.mutex.RUnlock() return v.value, v.timestamp } -// isText returns true if a significant prefix of s looks like correct UTF-8; +// IsText returns whether a significant prefix of s looks like correct UTF-8; // that is, if it is likely that s is human-readable text. -// -func isText(s []byte) bool { +func IsText(s []byte) bool { const max = 1024 // at least utf8.UTFMax if len(s) > max { s = s[0:max] @@ -62,12 +62,16 @@ var textExt = map[string]bool{ ".js": false, // must be served raw } -// isTextFile returns true if the file has a known extension indicating +// FileSystem is a minimal virtual filesystem. +type FileSystem interface { + Open(name string) (io.ReadCloser, error) +} + +// IsTextFile returns whether the file has a known extension indicating // a text file, or if a significant chunk of the specified file looks like // correct UTF-8; that is, if it is likely that the file contains human- // readable text. -// -func isTextFile(filename string) bool { +func IsTextFile(fs vfs.Opener, filename string) bool { // if the extension is known, use it for decision making if isText, found := textExt[pathpkg.Ext(filename)]; found { return isText @@ -87,5 +91,5 @@ func isTextFile(filename string) bool { return false } - return isText(buf[0:n]) + return IsText(buf[0:n]) } diff --git a/godoc/vfs/vfs.go b/godoc/vfs/vfs.go new file mode 100644 index 0000000000..55ba231bf3 --- /dev/null +++ b/godoc/vfs/vfs.go @@ -0,0 +1,23 @@ +// 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 vfs defines virtual filesystem types. +package vfs + +import ( + "io" +) + +// Opener is a minimal virtual filesystem that can only open +// regular files. +type Opener interface { + Open(name string) (ReadSeekCloser, error) +} + +// A ReadSeekCloser can Read, Seek, and Close. +type ReadSeekCloser interface { + io.Reader + io.Seeker + io.Closer +}