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:
parent
d79f4fe25b
commit
7526441b70
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
23
godoc/vfs/vfs.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user