1
0
mirror of https://github.com/golang/go synced 2024-11-18 11:04:42 -07:00

godoc: add util package, add start of vfs package

Move some code out of cmd/godoc.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/11413043
This commit is contained in:
Brad Fitzpatrick 2013-07-17 14:44:18 +10:00
parent d79f4fe25b
commit 7526441b70
7 changed files with 77 additions and 63 deletions

View File

@ -19,6 +19,8 @@ import (
"sort" "sort"
"strings" "strings"
"time" "time"
"code.google.com/p/go.tools/godoc/vfs"
) )
// fs is the file system that godoc reads from and serves. // 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 // The FileSystem interface specifies the methods godoc is using
// to access the file system for which it serves documentation. // to access the file system for which it serves documentation.
type FileSystem interface { type FileSystem interface {
Open(path string) (readSeekCloser, error) Open(path string) (vfs.ReadSeekCloser, error)
Lstat(path string) (os.FileInfo, error) Lstat(path string) (os.FileInfo, error)
Stat(path string) (os.FileInfo, error) Stat(path string) (os.FileInfo, error)
ReadDir(path string) ([]os.FileInfo, error) ReadDir(path string) ([]os.FileInfo, error)
String() string String() string
} }
type readSeekCloser interface {
io.Reader
io.Seeker
io.Closer
}
// ReadFile reads the file named by path from fs and returns the contents. // ReadFile reads the file named by path from fs and returns the contents.
func ReadFile(fs FileSystem, path string) ([]byte, error) { func ReadFile(fs FileSystem, path string) ([]byte, error) {
rc, err := fs.Open(path) rc, err := fs.Open(path)
@ -97,7 +93,7 @@ func (root osFS) resolve(path string) string {
return filepath.Join(string(root), path) 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)) f, err := os.Open(root.resolve(path))
if err != nil { if err != nil {
return nil, err return nil, err
@ -319,7 +315,7 @@ func (ns nameSpace) resolve(path string) []mountedFS {
} }
// Open implements the FileSystem Open method. // 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 var err error
for _, m := range ns.resolve(path) { for _, m := range ns.resolve(path) {
if debugNS { 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. // httpFile implements http.File for a file (not directory) in a FileSystem.
type httpFile struct { type httpFile struct {
fs FileSystem fs FileSystem
readSeekCloser vfs.ReadSeekCloser
name string name string
} }

View File

@ -32,26 +32,13 @@ import (
"time" "time"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"code.google.com/p/go.tools/godoc/util"
) )
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Globals // 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 ( var (
verbose = flag.Bool("v", false, "verbose mode") 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") indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
// file system information // file system information
fsTree RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now) fsTree util.RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
fsModified RWValue // timestamp of last call to invalidateIndex fsModified util.RWValue // timestamp of last call to invalidateIndex
docMetadata RWValue // mapping from paths to *Metadata docMetadata util.RWValue // mapping from paths to *Metadata
// http handlers // http handlers
fileServer http.Handler // default file server fileServer http.Handler // default file server
@ -112,7 +99,7 @@ func initFSTree() {
log.Println("Warning: FSTree is nil") log.Println("Warning: FSTree is nil")
return return
} }
fsTree.set(dir) fsTree.Set(dir)
invalidateIndex() invalidateIndex()
} }
@ -250,7 +237,7 @@ func infoKind_htmlFunc(info SpotInfo) string {
func infoLineFunc(info SpotInfo) int { func infoLineFunc(info SpotInfo) int {
line := info.Lori() line := info.Lori()
if info.IsIndex() { if info.IsIndex() {
index, _ := searchIndex.get() index, _ := searchIndex.Get()
if index != nil { if index != nil {
line = index.(*Index).Snippet(line).Line line = index.(*Index).Snippet(line).Line
} else { } else {
@ -266,7 +253,7 @@ func infoLineFunc(info SpotInfo) int {
func infoSnippet_htmlFunc(info SpotInfo) string { func infoSnippet_htmlFunc(info SpotInfo) string {
if info.IsIndex() { if info.IsIndex() {
index, _ := searchIndex.get() index, _ := searchIndex.Get()
// Snippet.Text was HTML-escaped when it was generated // Snippet.Text was HTML-escaped when it was generated
return index.(*Index).Snippet(info.Lori()).Text return index.(*Index).Snippet(info.Lori()).Text
} }
@ -838,7 +825,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
if redirect(w, r) { if redirect(w, r) {
return 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) serveHTMLDoc(w, r, index, index)
return return
} }
@ -846,7 +833,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
return return
} }
if isTextFile(abspath) { if util.IsTextFile(fs, abspath) {
if redirectFile(w, r) { if redirectFile(w, r) {
return return
} }
@ -1168,7 +1155,7 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) *Pag
// get directory information, if any // get directory information, if any
var dir *Directory var dir *Directory
var timestamp time.Time 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 // directory tree is present; lookup respective directory
// (may still fail if the file system was updated and the // (may still fail if the file system was updated and the
// new directory tree has not yet been computed) // new directory tree has not yet been computed)
@ -1256,7 +1243,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Search // Search
var searchIndex RWValue var searchIndex util.RWValue
type SearchResult struct { type SearchResult struct {
Query string Query string
@ -1276,7 +1263,7 @@ type SearchResult struct {
func lookup(query string) (result SearchResult) { func lookup(query string) (result SearchResult) {
result.Query = query result.Query = query
index, timestamp := searchIndex.get() index, timestamp := searchIndex.Get()
if index != nil { if index != nil {
index := index.(*Index) index := index.(*Index)
@ -1311,7 +1298,7 @@ func lookup(query string) (result SearchResult) {
// is the result accurate? // is the result accurate?
if *indexEnabled { 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. // The index is older than the latest file system change under godoc's observation.
result.Alert = "Indexing in progress: result may be inaccurate" result.Alert = "Indexing in progress: result may be inaccurate"
} }
@ -1422,7 +1409,7 @@ func updateMetadata() {
} }
} }
scan("/doc") scan("/doc")
docMetadata.set(metadata) docMetadata.Set(metadata)
} }
// Send a value on this channel to trigger a metadata refresh. // Send a value on this channel to trigger a metadata refresh.
@ -1455,7 +1442,7 @@ func refreshMetadataLoop() {
// exists. // exists.
// //
func metadataFor(relpath string) *Metadata { func metadataFor(relpath string) *Metadata {
if m, _ := docMetadata.get(); m != nil { if m, _ := docMetadata.Get(); m != nil {
meta := m.(map[string]*Metadata) meta := m.(map[string]*Metadata)
// If metadata for this relpath exists, return it. // If metadata for this relpath exists, return it.
if p := meta[relpath]; p != nil { 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. // under godoc's observation change so that the indexer is kicked on.
// //
func invalidateIndex() { func invalidateIndex() {
fsModified.set(nil) fsModified.Set(nil)
refreshMetadata() refreshMetadata()
} }
@ -1487,16 +1474,16 @@ func invalidateIndex() {
// than any of the file systems under godoc's observation. // than any of the file systems under godoc's observation.
// //
func indexUpToDate() bool { func indexUpToDate() bool {
_, fsTime := fsModified.get() _, fsTime := fsModified.Get()
_, siTime := searchIndex.get() _, siTime := searchIndex.Get()
return !fsTime.After(siTime) return !fsTime.After(siTime)
} }
// feedDirnames feeds the directory names of all directories // feedDirnames feeds the directory names of all directories
// under the file system given by root to channel c. // under the file system given by root to channel c.
// //
func feedDirnames(root *RWValue, c chan<- string) { func feedDirnames(root *util.RWValue, c chan<- string) {
if dir, _ := root.get(); dir != nil { if dir, _ := root.Get(); dir != nil {
for d := range dir.(*Directory).iter(false) { for d := range dir.(*Directory).iter(false) {
c <- d.Path c <- d.Path
} }
@ -1536,7 +1523,7 @@ func readIndex(filenames string) error {
if err := x.Read(io.MultiReader(files...)); err != nil { if err := x.Read(io.MultiReader(files...)); err != nil {
return err return err
} }
searchIndex.set(x) searchIndex.Set(x)
return nil return nil
} }
@ -1547,7 +1534,7 @@ func updateIndex() {
start := time.Now() start := time.Now()
index := NewIndex(fsDirnames(), *maxResults > 0, *indexThrottle) index := NewIndex(fsDirnames(), *maxResults > 0, *indexThrottle)
stop := time.Now() stop := time.Now()
searchIndex.set(index) searchIndex.Set(index)
if *verbose { if *verbose {
secs := stop.Sub(start).Seconds() secs := stop.Sub(start).Seconds()
stats := index.Stats() stats := index.Stats()

View File

@ -54,6 +54,8 @@ import (
"strings" "strings"
"time" "time"
"unicode" "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 return
} }
if isText(src) { if util.IsText(src) {
// only add the file to the file set (for the full text index) // only add the file to the file set (for the full text index)
file = x.fset.AddFile(filename, x.fset.Base(), len(src)) file = x.fset.AddFile(filename, x.fset.Base(), len(src))
file.SetLinesForContent(src) file.SetLinesForContent(src)

View File

@ -212,7 +212,7 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
index, _ := searchIndex.get() index, _ := searchIndex.Get()
err = index.(*Index).Write(f) err = index.(*Index).Write(f)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -27,6 +27,8 @@ import (
"sort" "sort"
"strings" "strings"
"time" "time"
"code.google.com/p/go.tools/godoc/vfs"
) )
// zipFI is the zip-file based implementation of FileInfo // 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 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)) _, fi, err := fs.stat(zipPath(abspath))
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// This file contains support functionality for godoc. // Package util contains utility types and functions for godoc.
package util
package main
import ( import (
"io"
pathpkg "path" pathpkg "path"
"sync" "sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
"code.google.com/p/go.tools/godoc/vfs"
) )
// An RWValue wraps a value and permits mutually exclusive // An RWValue wraps a value and permits mutually exclusive
// access to it and records the time the value was last set. // access to it and records the time the value was last set.
//
type RWValue struct { type RWValue struct {
mutex sync.RWMutex mutex sync.RWMutex
value interface{} value interface{}
timestamp time.Time // time of last set() timestamp time.Time // time of last set()
} }
func (v *RWValue) set(value interface{}) { func (v *RWValue) Set(value interface{}) {
v.mutex.Lock() v.mutex.Lock()
v.value = value v.value = value
v.timestamp = time.Now() v.timestamp = time.Now()
v.mutex.Unlock() v.mutex.Unlock()
} }
func (v *RWValue) get() (interface{}, time.Time) { func (v *RWValue) Get() (interface{}, time.Time) {
v.mutex.RLock() v.mutex.RLock()
defer v.mutex.RUnlock() defer v.mutex.RUnlock()
return v.value, v.timestamp 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. // 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 const max = 1024 // at least utf8.UTFMax
if len(s) > max { if len(s) > max {
s = s[0:max] s = s[0:max]
@ -62,12 +62,16 @@ var textExt = map[string]bool{
".js": false, // must be served raw ".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 // 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- // correct UTF-8; that is, if it is likely that the file contains human-
// readable text. // readable text.
// func IsTextFile(fs vfs.Opener, filename string) bool {
func isTextFile(filename string) bool {
// if the extension is known, use it for decision making // if the extension is known, use it for decision making
if isText, found := textExt[pathpkg.Ext(filename)]; found { if isText, found := textExt[pathpkg.Ext(filename)]; found {
return isText return isText
@ -87,5 +91,5 @@ func isTextFile(filename string) bool {
return false return false
} }
return isText(buf[0:n]) return IsText(buf[0:n])
} }

23
godoc/vfs/vfs.go Normal file
View File

@ -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
}