1
0
mirror of https://github.com/golang/go synced 2024-09-30 16:28:32 -06:00

godoc: move bulk of the code to the package

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/11424043
This commit is contained in:
Brad Fitzpatrick 2013-07-17 17:09:54 +10:00
parent 2392be72c2
commit e6ff53bcc8
14 changed files with 320 additions and 1461 deletions

View File

@ -26,6 +26,9 @@ import (
"strings"
"text/template"
"unicode/utf8"
"code.google.com/p/go.tools/godoc"
"code.google.com/p/go.tools/godoc/vfs"
)
// Handler for /doc/codewalk/ and below.
@ -40,7 +43,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
}
// If directory exists, serve list of code walks.
dir, err := fs.Lstat(abspath)
dir, err := godoc.FS.Lstat(abspath)
if err == nil && dir.IsDir() {
codewalkDir(w, r, relpath, abspath)
return
@ -59,7 +62,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
cw, err := loadCodewalk(abspath + ".xml")
if err != nil {
log.Print(err)
serveError(w, r, relpath, err)
godoc.ServeError(w, r, relpath, err)
return
}
@ -68,7 +71,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
return
}
servePage(w, Page{
godoc.ServePage(w, godoc.Page{
Title: "Codewalk: " + cw.Title,
Tabtitle: cw.Title,
Body: applyTemplate(codewalkHTML, "codewalk", cw),
@ -114,7 +117,7 @@ func (st *Codestep) String() string {
// loadCodewalk reads a codewalk from the named XML file.
func loadCodewalk(filename string) (*Codewalk, error) {
f, err := fs.Open(filename)
f, err := godoc.FS.Open(filename)
if err != nil {
return nil, err
}
@ -135,7 +138,7 @@ func loadCodewalk(filename string) (*Codewalk, error) {
i = len(st.Src)
}
filename := st.Src[0:i]
data, err := ReadFile(fs, filename)
data, err := vfs.ReadFile(godoc.FS, filename)
if err != nil {
st.Err = err
continue
@ -182,10 +185,10 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
Title string
}
dir, err := fs.ReadDir(abspath)
dir, err := godoc.FS.ReadDir(abspath)
if err != nil {
log.Print(err)
serveError(w, r, relpath, err)
godoc.ServeError(w, r, relpath, err)
return
}
var v []interface{}
@ -202,7 +205,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
}
}
servePage(w, Page{
godoc.ServePage(w, godoc.Page{
Title: "Codewalks",
Body: applyTemplate(codewalkdirHTML, "codewalkdir", v),
})
@ -216,10 +219,10 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
// the usual godoc HTML wrapper.
func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
abspath := f
data, err := ReadFile(fs, abspath)
data, err := vfs.ReadFile(godoc.FS, abspath)
if err != nil {
log.Print(err)
serveError(w, r, f, err)
godoc.ServeError(w, r, f, err)
return
}
lo, _ := strconv.Atoi(r.FormValue("lo"))

View File

@ -1,48 +0,0 @@
// Copyright 2011 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 defines types for abstract file system access and
// provides an implementation accessing the file system of the
// underlying OS.
package main
import (
"io/ioutil"
"code.google.com/p/go.tools/godoc/vfs"
)
// fs is the file system that godoc reads from and serves.
// It is a virtual file system that operates on slash-separated paths,
// and its root corresponds to the Go distribution root: /src/pkg
// 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.
//
// New file trees - implementations of FileSystem - can be added to
// the virtual file system using nameSpace's Bind method.
// The usual setup is to bind OS(runtime.GOROOT) to the root
// of the name space and then bind any GOPATH/src directories
// on top of /src/pkg, so that all sources are in /src/pkg.
//
// For more about name spaces, see the NameSpace type's
// documentation in code.google.com/p/go.tools/godoc/vfs.
//
// The use of this virtual file system means that most code processing
// paths can assume they are slash-separated and should be using
// package path (often imported as pathpkg) to manipulate them,
// even on Windows.
//
var fs = vfs.NameSpace{} // the underlying file system for godoc
// ReadFile reads the file named by path from fs and returns the contents.
func ReadFile(fs vfs.FileSystem, path string) ([]byte, error) {
rc, err := fs.Open(path)
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}

File diff suppressed because it is too large Load Diff

View File

@ -49,6 +49,7 @@ import (
"runtime"
"strings"
"code.google.com/p/go.tools/godoc"
"code.google.com/p/go.tools/godoc/vfs"
"code.google.com/p/go.tools/godoc/vfs/zipfs"
)
@ -76,15 +77,6 @@ var (
query = flag.Bool("q", false, "arguments are considered search queries")
)
func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
w.WriteHeader(http.StatusNotFound)
servePage(w, Page{
Title: "File " + relpath,
Subtitle: relpath,
Body: applyTemplate(errorHTML, "errorHTML", err), // err may contain an absolute path!
})
}
func usage() {
fmt.Fprintf(os.Stderr,
"usage: godoc package [name ...]\n"+
@ -165,8 +157,8 @@ func main() {
usage()
}
if *tabwidth < 0 {
log.Fatalf("negative tabwidth %d", *tabwidth)
if godoc.TabWidth < 0 {
log.Fatalf("negative tabwidth %d", godoc.TabWidth)
}
// Determine file system to use.
@ -175,9 +167,9 @@ func main() {
// same is true for the http handlers in initHandlers.
if *zipfile == "" {
// use file system of underlying OS
fs.Bind("/", vfs.OS(*goroot), "/", vfs.BindReplace)
godoc.FS.Bind("/", vfs.OS(*goroot), "/", vfs.BindReplace)
if *templateDir != "" {
fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
godoc.FS.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
}
} else {
// use file system specified via .zip file (path separator must be '/')
@ -186,20 +178,20 @@ func main() {
log.Fatalf("%s: %s\n", *zipfile, err)
}
defer rc.Close() // be nice (e.g., -writeIndex mode)
fs.Bind("/", zipvfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
godoc.FS.Bind("/", zipvfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
}
// Bind $GOPATH trees into Go root.
for _, p := range filepath.SplitList(build.Default.GOPATH) {
fs.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
godoc.FS.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
}
readTemplates()
initHandlers()
godoc.InitHandlers(godoc.FS)
if *writeIndex {
// Write search index and exit.
if *indexFiles == "" {
if godoc.IndexFiles == "" {
log.Fatal("no index file specified")
}
@ -207,16 +199,16 @@ func main() {
*verbose = true // want to see what happens
initFSTree()
*indexThrottle = 1
updateIndex()
godoc.IndexThrottle = 1.0
godoc.UpdateIndex()
log.Println("writing index file", *indexFiles)
f, err := os.Create(*indexFiles)
log.Println("writing index file", godoc.IndexFiles)
f, err := os.Create(godoc.IndexFiles)
if err != nil {
log.Fatal(err)
}
index, _ := searchIndex.Get()
err = index.(*Index).Write(f)
index, _ := godoc.SearchIndex.Get()
err = index.(*godoc.Index).Write(f)
if err != nil {
log.Fatal(err)
}
@ -229,7 +221,7 @@ func main() {
if *urlFlag != "" {
registerPublicHandlers(http.DefaultServeMux)
initFSTree()
updateMetadata()
godoc.UpdateMetadata()
// Try up to 10 fetches, following redirects.
urlstr := *urlFlag
for i := 0; i < 10; i++ {
@ -273,16 +265,16 @@ func main() {
log.Printf("version = %s", runtime.Version())
log.Printf("address = %s", *httpAddr)
log.Printf("goroot = %s", *goroot)
log.Printf("tabwidth = %d", *tabwidth)
log.Printf("tabwidth = %d", godoc.TabWidth)
switch {
case !*indexEnabled:
case !godoc.IndexEnabled:
log.Print("search index disabled")
case *maxResults > 0:
log.Printf("full text index enabled (maxresults = %d)", *maxResults)
case godoc.MaxResults > 0:
log.Printf("full text index enabled (maxresults = %d)", godoc.MaxResults)
default:
log.Print("identifier search index enabled")
}
fs.Fprint(os.Stderr)
godoc.FS.Fprint(os.Stderr)
handler = loggingHandler(handler)
}
@ -293,13 +285,13 @@ func main() {
go initFSTree()
// Immediately update metadata.
updateMetadata()
godoc.UpdateMetadata()
// Periodically refresh metadata.
go refreshMetadataLoop()
go godoc.RefreshMetadataLoop()
// Initialize search index.
if *indexEnabled {
go indexer()
if godoc.IndexEnabled {
go godoc.RunIndexer()
}
// Start http server.
@ -310,10 +302,11 @@ func main() {
return
}
packageText := godoc.PackageText
// Command line mode.
if *html {
packageText = packageHTML
searchText = packageHTML
packageText = godoc.PackageHTML
}
if *query {
@ -341,52 +334,52 @@ func main() {
var forceCmd bool
var abspath, relpath string
if filepath.IsAbs(path) {
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
godoc.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)
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
godoc.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 != "" {
fs.Bind(target, vfs.OS(bp.Dir), "/", vfs.BindReplace)
godoc.FS.Bind(target, vfs.OS(bp.Dir), "/", vfs.BindReplace)
abspath = target
relpath = bp.ImportPath
} else {
abspath = pathpkg.Join(pkgHandler.fsRoot, path)
abspath = pathpkg.Join(godoc.PkgHandler.FSRoot(), path)
}
if relpath == "" {
relpath = abspath
}
var mode PageInfoMode
if relpath == builtinPkgPath {
var mode godoc.PageInfoMode
if relpath == godoc.BuiltinPkgPath {
// the fake built-in package contains unexported identifiers
mode = noFiltering
mode = godoc.NoFiltering
}
if *srcMode {
// only filter exports if we don't have explicit command-line filter arguments
if flag.NArg() > 1 {
mode |= noFiltering
mode |= godoc.NoFiltering
}
mode |= showSource
mode |= godoc.ShowSource
}
// first, try as package unless forced as command
var info *PageInfo
var info *godoc.PageInfo
if !forceCmd {
info = pkgHandler.getPageInfo(abspath, relpath, mode)
info = godoc.PkgHandler.GetPageInfo(abspath, relpath, mode)
}
// second, try as command unless the path is absolute
// (the go command invokes godoc w/ absolute paths; don't override)
var cinfo *PageInfo
var cinfo *godoc.PageInfo
if !filepath.IsAbs(path) {
abspath = pathpkg.Join(cmdHandler.fsRoot, path)
cinfo = cmdHandler.getPageInfo(abspath, relpath, mode)
abspath = pathpkg.Join(godoc.CmdHandler.FSRoot(), path)
cinfo = godoc.CmdHandler.GetPageInfo(abspath, relpath, mode)
}
// determine what to use
@ -442,10 +435,10 @@ func main() {
}
if *html {
var buf bytes.Buffer
writeNode(&buf, info.FSet, cn)
FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil)
godoc.WriteNode(&buf, info.FSet, cn)
godoc.FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil)
} else {
writeNode(os.Stdout, info.FSet, cn)
godoc.WriteNode(os.Stdout, info.FSet, cn)
}
fmt.Println()
}

View File

@ -2,6 +2,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build playground
// TODO(bradfitz,adg): move the
// code.google.com/p/go.talks/pkg/playground package elsewhere, so
// go.tools doesn't depend on go.talks.
package main
import (

View File

@ -4,7 +4,7 @@
// This file contains the code dealing with package directory trees.
package main
package godoc
import (
"bytes"
@ -69,7 +69,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
}
}
list, _ := fs.ReadDir(path)
list, _ := FS.ReadDir(path)
// determine number of subdirectories and if there are package files
ndirs := 0
@ -161,9 +161,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 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 := 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 {

View File

@ -8,7 +8,7 @@
// built on top of FormatSelections, a generic formatter
// for "selected" text.
package main
package godoc
import (
"fmt"
@ -284,11 +284,10 @@ func regexpSelection(text []byte, expr string) Selection {
var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`)
// rangeSelection computes the Selection for a text range described
// RangeSelection computes the Selection for a text range described
// by the argument str; the range description must match the selRx
// regular expression.
//
func rangeSelection(str string) Selection {
func RangeSelection(str string) Selection {
m := selRx.FindStringSubmatch(str)
if len(m) >= 2 {
from, _ := strconv.Atoi(m[1])

View File

@ -35,21 +35,25 @@
// - translate the Pos values back into file and line information and
// sort the result
package main
package godoc
import (
"bufio"
"bytes"
"encoding/gob"
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"index/suffixarray"
"io"
"log"
"os"
pathpkg "path"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"time"
@ -58,6 +62,19 @@ import (
"code.google.com/p/go.tools/godoc/util"
)
// TODO(bradfitz,adg): legacy flag vars. clean up.
var (
MaxResults = 1000
// index throttle value; 0.0 = no time allocated, 1.0 = full throttle
IndexThrottle float64 = 0.75
// IndexFiles is a glob pattern specifying index files; if
// not empty, the index is read from these files in sorted
// order")
IndexFiles string
)
// ----------------------------------------------------------------------------
// InterfaceSlice is a helper type for sorting interface
// slices according to some slice-specific sort criteria.
@ -116,67 +133,6 @@ func (h RunList) reduce(less Comparer, newRun func(h RunList) interface{}) RunLi
return hh
}
// ----------------------------------------------------------------------------
// SpotInfo
// A SpotInfo value describes a particular identifier spot in a given file;
// It encodes three values: the SpotKind (declaration or use), a line or
// snippet index "lori", and whether it's a line or index.
//
// The following encoding is used:
//
// bits 32 4 1 0
// value [lori|kind|isIndex]
//
type SpotInfo uint32
// SpotKind describes whether an identifier is declared (and what kind of
// declaration) or used.
type SpotKind uint32
const (
PackageClause SpotKind = iota
ImportDecl
ConstDecl
TypeDecl
VarDecl
FuncDecl
MethodDecl
Use
nKinds
)
func init() {
// sanity check: if nKinds is too large, the SpotInfo
// accessor functions may need to be updated
if nKinds > 8 {
panic("internal error: nKinds > 8")
}
}
// makeSpotInfo makes a SpotInfo.
func makeSpotInfo(kind SpotKind, lori int, isIndex bool) SpotInfo {
// encode lori: bits [4..32)
x := SpotInfo(lori) << 4
if int(x>>4) != lori {
// lori value doesn't fit - since snippet indices are
// most certainly always smaller then 1<<28, this can
// only happen for line numbers; give it no line number (= 0)
x = 0
}
// encode kind: bits [1..4)
x |= SpotInfo(kind) << 1
// encode isIndex: bit 0
if isIndex {
x |= 1
}
return x
}
func (x SpotInfo) Kind() SpotKind { return SpotKind(x >> 1 & 7) }
func (x SpotInfo) Lori() int { return int(x >> 4) }
func (x SpotInfo) IsIndex() bool { return x&1 != 0 }
// ----------------------------------------------------------------------------
// KindRun
@ -482,8 +438,8 @@ func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
}
}
func (x *Indexer) visitFieldList(kind SpotKind, list *ast.FieldList) {
for _, f := range list.List {
func (x *Indexer) visitFieldList(kind SpotKind, flist *ast.FieldList) {
for _, f := range flist.List {
x.decl = nil // no snippets for fields
for _, name := range f.Names {
x.visitIdent(kind, name)
@ -593,7 +549,7 @@ func pkgName(filename string) string {
// failed (that is, if the file was not added), it returns file == nil.
func (x *Indexer) addFile(filename string, goFile bool) (file *token.File, ast *ast.File) {
// open file
f, err := fs.Open(filename)
f, err := FS.Open(filename)
if err != nil {
return
}
@ -772,7 +728,7 @@ func NewIndex(dirnames <-chan string, fulltextIndex bool, throttle float64) *Ind
// index all files in the directories given by dirnames
for dirname := range dirnames {
list, err := fs.ReadDir(dirname)
list, err := FS.ReadDir(dirname)
if err != nil {
continue // ignore this directory
}
@ -1079,3 +1035,113 @@ func (x *Index) LookupRegexp(r *regexp.Regexp, n int) (found int, result []FileL
return
}
// 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()
}
// indexUpToDate() returns true if the search index is not older
// than any of the file systems under godoc's observation.
//
func indexUpToDate() bool {
_, 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 *util.RWValue, c chan<- string) {
if dir, _ := root.Get(); dir != nil {
for d := range dir.(*Directory).iter(false) {
c <- d.Path
}
}
}
// fsDirnames() returns a channel sending all directory names
// of all the file systems under godoc's observation.
//
func fsDirnames() <-chan string {
c := make(chan string, 256) // buffered for fewer context switches
go func() {
feedDirnames(&FSTree, c)
close(c)
}()
return c
}
func readIndex(filenames string) error {
matches, err := filepath.Glob(filenames)
if err != nil {
return err
} else if matches == nil {
return fmt.Errorf("no index files match %q", filenames)
}
sort.Strings(matches) // make sure files are in the right order
files := make([]io.Reader, 0, len(matches))
for _, filename := range matches {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
files = append(files, f)
}
x := new(Index)
if err := x.Read(io.MultiReader(files...)); err != nil {
return err
}
SearchIndex.Set(x)
return nil
}
func UpdateIndex() {
if Verbose {
log.Printf("updating index...")
}
start := time.Now()
index := NewIndex(fsDirnames(), MaxResults > 0, IndexThrottle)
stop := time.Now()
SearchIndex.Set(index)
if Verbose {
secs := stop.Sub(start).Seconds()
stats := index.Stats()
log.Printf("index updated (%gs, %d bytes of source, %d files, %d lines, %d unique words, %d spots)",
secs, stats.Bytes, stats.Files, stats.Lines, stats.Words, stats.Spots)
}
memstats := new(runtime.MemStats)
runtime.ReadMemStats(memstats)
log.Printf("before GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
runtime.GC()
runtime.ReadMemStats(memstats)
log.Printf("after GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
}
// RunIndexer runs forever, indexing.
func RunIndexer() {
// initialize the index from disk if possible
if IndexFiles != "" {
if err := readIndex(IndexFiles); err != nil {
log.Printf("error reading index: %s", err)
}
}
// repeatedly update the index when it goes out of date
for {
if !indexUpToDate() {
// index possibly out of date - make a new one
UpdateIndex()
}
delay := 60 * time.Second // by default, try every 60s
if false { // TODO(bradfitz): was: *testDir != "" {
// in test mode, try once a second for fast startup
delay = 1 * time.Second
}
time.Sleep(delay)
}
}

View File

@ -8,7 +8,7 @@
// doesn't have complete type information, but it's
// reasonably good for browsing.
package main
package godoc
import (
"fmt"
@ -94,7 +94,7 @@ func linksFor(node ast.Node) (list []link) {
switch m {
case identUse:
if n.Obj == nil && predeclared[n.Name] {
info.path = builtinPkgPath
info.path = BuiltinPkgPath
}
info.name = n.Name
case identDef:

View File

@ -5,17 +5,19 @@
// This file contains support functions for parsing .go files
// accessed via godoc's file system fs.
package main
package godoc
import (
"go/ast"
"go/parser"
"go/token"
pathpkg "path"
"code.google.com/p/go.tools/godoc/vfs"
)
func parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
src, err := ReadFile(fs, filename)
src, err := vfs.ReadFile(FS, filename)
if err != nil {
return nil, err
}

View File

@ -7,7 +7,7 @@
//
// Note: At the moment, this only creates HTML snippets.
package main
package godoc
import (
"bytes"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
package godoc
// This file contains the mechanism to "linkify" html source
// text containing EBNF sections (as found in go_spec.html).

View File

@ -29,7 +29,7 @@
// lines in the input that will not appear in the output but are easy
// to identify by pattern.
package main
package godoc
import (
"bytes"
@ -38,19 +38,21 @@ import (
"regexp"
"strings"
"text/template"
"code.google.com/p/go.tools/godoc/vfs"
)
// Functions in this file panic on error, but the panic is recovered
// to an error by 'code'.
var templateFuncs = template.FuncMap{
var TemplateFuncs = template.FuncMap{
"code": code,
}
// contents reads and returns the content of the named file
// (from the virtual file system, so for example /doc refers to $GOROOT/doc).
func contents(name string) string {
file, err := ReadFile(fs, name)
file, err := vfs.ReadFile(FS, name)
if err != nil {
log.Panic(err)
}

View File

@ -8,6 +8,7 @@ package vfs
import (
"io"
"io/ioutil"
"os"
)
@ -32,3 +33,13 @@ type ReadSeekCloser interface {
io.Seeker
io.Closer
}
// ReadFile reads the file named by path from fs and returns the contents.
func ReadFile(fs Opener, path string) ([]byte, error) {
rc, err := fs.Open(path)
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}