1
0
mirror of https://github.com/golang/go synced 2024-11-25 00:07:56 -07:00

godoc: replace direct OS file system accesses in favor

of accesses via a FileSystem interface.

Preparation for appengine version which gets its files
via a snapshot or zip file and uses a corresponding
FileSystem implementation.

R=rsc, r
CC=golang-dev
https://golang.org/cl/4572065
This commit is contained in:
Robert Griesemer 2011-06-15 14:06:35 -07:00
parent e3d2a29e99
commit fb9ea79916
10 changed files with 237 additions and 69 deletions

View File

@ -8,11 +8,13 @@ TARG=godoc
GOFILES=\ GOFILES=\
codewalk.go\ codewalk.go\
dirtrees.go\ dirtrees.go\
filesystem.go\
format.go\ format.go\
godoc.go\ godoc.go\
index.go\ index.go\
main.go\ main.go\
mapping.go\ mapping.go\
parser.go\
snippet.go\ snippet.go\
spec.go\ spec.go\
utils.go\ utils.go\

View File

@ -17,7 +17,6 @@ import (
"fmt" "fmt"
"http" "http"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"regexp" "regexp"
@ -42,7 +41,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
} }
// If directory exists, serve list of code walks. // If directory exists, serve list of code walks.
dir, err := os.Lstat(abspath) dir, err := fs.Lstat(abspath)
if err == nil && dir.IsDirectory() { if err == nil && dir.IsDirectory() {
codewalkDir(w, r, relpath, abspath) codewalkDir(w, r, relpath, abspath)
return return
@ -114,8 +113,8 @@ func (st *Codestep) String() string {
// loadCodewalk reads a codewalk from the named XML file. // loadCodewalk reads a codewalk from the named XML file.
func loadCodewalk(file string) (*Codewalk, os.Error) { func loadCodewalk(filename string) (*Codewalk, os.Error) {
f, err := os.Open(file) f, err := fs.Open(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -125,7 +124,7 @@ func loadCodewalk(file string) (*Codewalk, os.Error) {
p.Entity = xml.HTMLEntity p.Entity = xml.HTMLEntity
err = p.Unmarshal(cw, nil) err = p.Unmarshal(cw, nil)
if err != nil { if err != nil {
return nil, &os.PathError{"parsing", file, err} return nil, &os.PathError{"parsing", filename, err}
} }
// Compute file list, evaluate line numbers for addresses. // Compute file list, evaluate line numbers for addresses.
@ -135,8 +134,8 @@ func loadCodewalk(file string) (*Codewalk, os.Error) {
if i < 0 { if i < 0 {
i = len(st.Src) i = len(st.Src)
} }
file := st.Src[0:i] filename := st.Src[0:i]
data, err := ioutil.ReadFile(absolutePath(file, *goroot)) data, err := fs.ReadFile(absolutePath(filename, *goroot))
if err != nil { if err != nil {
st.Err = err st.Err = err
continue continue
@ -158,8 +157,8 @@ func loadCodewalk(file string) (*Codewalk, os.Error) {
st.Hi = byteToLine(data, hi-1) st.Hi = byteToLine(data, hi-1)
} }
st.Data = data st.Data = data
st.File = file st.File = filename
m[file] = true m[filename] = true
} }
// Make list of files // Make list of files
@ -184,7 +183,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
Title string Title string
} }
dir, err := ioutil.ReadDir(abspath) dir, err := fs.ReadDir(abspath)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
serveError(w, r, relpath, err) serveError(w, r, relpath, err)
@ -192,14 +191,15 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
} }
var v vector.Vector var v vector.Vector
for _, fi := range dir { for _, fi := range dir {
name := fi.Name()
if fi.IsDirectory() { if fi.IsDirectory() {
v.Push(&elem{fi.Name + "/", ""}) v.Push(&elem{name + "/", ""})
} else if strings.HasSuffix(fi.Name, ".xml") { } else if strings.HasSuffix(name, ".xml") {
cw, err := loadCodewalk(abspath + "/" + fi.Name) cw, err := loadCodewalk(abspath + "/" + name)
if err != nil { if err != nil {
continue continue
} }
v.Push(&elem{fi.Name[0 : len(fi.Name)-len(".xml")], cw.Title}) v.Push(&elem{name[0 : len(name)-len(".xml")], cw.Title})
} }
} }
@ -216,7 +216,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
// the usual godoc HTML wrapper. // the usual godoc HTML wrapper.
func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) { func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
abspath := absolutePath(f, *goroot) abspath := absolutePath(f, *goroot)
data, err := ioutil.ReadFile(abspath) data, err := fs.ReadFile(abspath)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
serveError(w, r, f, err) serveError(w, r, f, err)

View File

@ -11,9 +11,7 @@ import (
"go/doc" "go/doc"
"go/parser" "go/parser"
"go/token" "go/token"
"io/ioutil"
"log" "log"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"unicode" "unicode"
@ -29,21 +27,23 @@ type Directory struct {
} }
func isGoFile(f *os.FileInfo) bool { func isGoFile(fi FileInfo) bool {
return f.IsRegular() && name := fi.Name()
!strings.HasPrefix(f.Name, ".") && // ignore .files return fi.IsRegular() &&
filepath.Ext(f.Name) == ".go" !strings.HasPrefix(name, ".") && // ignore .files
filepath.Ext(name) == ".go"
} }
func isPkgFile(f *os.FileInfo) bool { func isPkgFile(fi FileInfo) bool {
return isGoFile(f) && return isGoFile(fi) &&
!strings.HasSuffix(f.Name, "_test.go") // ignore test files !strings.HasSuffix(fi.Name(), "_test.go") // ignore test files
} }
func isPkgDir(f *os.FileInfo) bool { func isPkgDir(fi FileInfo) bool {
return f.IsDirectory() && len(f.Name) > 0 && f.Name[0] != '_' name := fi.Name()
return fi.IsDirectory() && len(name) > 0 && name[0] != '_'
} }
@ -101,12 +101,12 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
return &Directory{depth, path, name, "", nil} return &Directory{depth, path, name, "", nil}
} }
list, err := ioutil.ReadDir(path) list, err := fs.ReadDir(path)
if err != nil { if err != nil {
// newDirTree is called with a path that should be a package // newDirTree is called with a path that should be a package
// directory; errors here should not happen, but if they do, // directory; errors here should not happen, but if they do,
// we want to know about them // we want to know about them
log.Printf("ioutil.ReadDir(%s): %s", path, err) log.Printf("ReadDir(%s): %s", path, err)
} }
// determine number of subdirectories and if there are package files // determine number of subdirectories and if there are package files
@ -123,7 +123,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
// though the directory doesn't contain any real package files - was bug) // though the directory doesn't contain any real package files - was bug)
if synopses[0] == "" { if synopses[0] == "" {
// no "optimal" package synopsis yet; continue to collect synopses // no "optimal" package synopsis yet; continue to collect synopses
file, err := parser.ParseFile(fset, filepath.Join(path, d.Name), nil, file, err := parser.ParseFile(fset, filepath.Join(path, d.Name()), nil,
parser.ParseComments|parser.PackageClauseOnly) parser.ParseComments|parser.PackageClauseOnly)
if err == nil { if err == nil {
hasPkgFiles = true hasPkgFiles = true
@ -156,7 +156,8 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
i := 0 i := 0
for _, d := range list { for _, d := range list {
if isPkgDir(d) { if isPkgDir(d) {
dd := b.newDirTree(fset, filepath.Join(path, d.Name), d.Name, depth+1) name := d.Name()
dd := b.newDirTree(fset, filepath.Join(path, name), name, depth+1)
if dd != nil { if dd != nil {
dirs[i] = dd dirs[i] = dd
i++ i++
@ -195,8 +196,8 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
// (i.e., in this case the tree may contain directories w/o any package files). // (i.e., in this case the tree may contain directories w/o any package files).
// //
func newDirectory(root string, pathFilter func(string) bool, maxDepth int) *Directory { func newDirectory(root string, pathFilter func(string) bool, maxDepth int) *Directory {
// The root could be a symbolic link so use os.Stat not os.Lstat. // The root could be a symbolic link so use Stat not Lstat.
d, err := os.Stat(root) d, err := fs.Stat(root)
// If we fail here, report detailed error messages; otherwise // If we fail here, report detailed error messages; otherwise
// is is hard to see why a directory tree was not built. // is is hard to see why a directory tree was not built.
switch { switch {
@ -213,7 +214,7 @@ func newDirectory(root string, pathFilter func(string) bool, maxDepth int) *Dire
b := treeBuilder{pathFilter, maxDepth} b := treeBuilder{pathFilter, maxDepth}
// the file set provided is only for local parsing, no position // the file set provided is only for local parsing, no position
// information escapes and thus we don't need to save the set // information escapes and thus we don't need to save the set
return b.newDirTree(token.NewFileSet(), root, d.Name, 0) return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
} }

View File

@ -0,0 +1,96 @@
// 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 abstract file system access.
package main
import (
"io"
"io/ioutil"
"os"
)
// The FileInfo interface provides access to file information.
type FileInfo interface {
Name() string
Size() int64
IsRegular() bool
IsDirectory() bool
}
// 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) (io.ReadCloser, os.Error)
Lstat(path string) (FileInfo, os.Error)
Stat(path string) (FileInfo, os.Error)
ReadDir(path string) ([]FileInfo, os.Error)
ReadFile(path string) ([]byte, os.Error)
}
// ----------------------------------------------------------------------------
// OS-specific FileSystem implementation
var OS FileSystem = osFS{}
// osFI is the OS-specific implementation of FileInfo.
type osFI struct {
*os.FileInfo
}
func (fi osFI) Name() string {
return fi.FileInfo.Name
}
func (fi osFI) Size() int64 {
if fi.IsDirectory() {
return 0
}
return fi.FileInfo.Size
}
// osFS is the OS-specific implementation of FileSystem
type osFS struct{}
func (osFS) Open(path string) (io.ReadCloser, os.Error) {
return os.Open(path)
}
func (osFS) Lstat(path string) (FileInfo, os.Error) {
fi, err := os.Lstat(path)
return osFI{fi}, err
}
func (osFS) Stat(path string) (FileInfo, os.Error) {
fi, err := os.Stat(path)
return osFI{fi}, err
}
func (osFS) ReadDir(path string) ([]FileInfo, os.Error) {
l0, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
l1 := make([]FileInfo, len(l0))
for i, e := range l0 {
l1[i] = osFI{e}
}
return l1, nil
}
func (osFS) ReadFile(path string) ([]byte, os.Error) {
return ioutil.ReadFile(path)
}

View File

@ -10,12 +10,10 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/doc" "go/doc"
"go/parser"
"go/printer" "go/printer"
"go/token" "go/token"
"http" "http"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"path" "path"
@ -71,6 +69,7 @@ var (
maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown") maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
// file system mapping // file system mapping
fs FileSystem // the underlying file system
fsMap Mapping // user-defined mapping fsMap Mapping // user-defined mapping
fsTree RWValue // *Directory tree of packages, updated with each sync fsTree RWValue // *Directory tree of packages, updated with each sync
pathFilter RWValue // filter used when building fsMap directory trees pathFilter RWValue // filter used when building fsMap directory trees
@ -147,13 +146,13 @@ func getPathFilter() func(string) bool {
// readDirList reads a file containing a newline-separated list // readDirList reads a file containing a newline-separated list
// of directory paths and returns the list of paths. // of directory paths and returns the list of paths.
func readDirList(filename string) ([]string, os.Error) { func readDirList(filename string) ([]string, os.Error) {
contents, err := ioutil.ReadFile(filename) contents, err := fs.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// create a sorted list of valid directory names // create a sorted list of valid directory names
filter := func(path string) bool { filter := func(path string) bool {
d, e := os.Lstat(path) d, e := fs.Lstat(path)
if e != nil && err == nil { if e != nil && err == nil {
// remember first error and return it from readDirList // remember first error and return it from readDirList
// so we have at least some information if things go bad // so we have at least some information if things go bad
@ -598,7 +597,7 @@ func timeFmt(w io.Writer, format string, x ...interface{}) {
// Template formatter for "dir/" format. // Template formatter for "dir/" format.
func dirslashFmt(w io.Writer, format string, x ...interface{}) { func dirslashFmt(w io.Writer, format string, x ...interface{}) {
if x[0].(*os.FileInfo).IsDirectory() { if x[0].(FileInfo).IsDirectory() {
w.Write([]byte{'/'}) w.Write([]byte{'/'})
} }
} }
@ -642,12 +641,12 @@ func readTemplate(name string) *template.Template {
if *templateDir != "" { if *templateDir != "" {
defaultpath := path defaultpath := path
path = filepath.Join(*templateDir, name) path = filepath.Join(*templateDir, name)
if _, err := os.Stat(path); err != nil { if _, err := fs.Stat(path); err != nil {
log.Print("readTemplate:", err) log.Print("readTemplate:", err)
path = defaultpath path = defaultpath
} }
} }
data, err := ioutil.ReadFile(path) data, err := fs.ReadFile(path)
if err != nil { if err != nil {
log.Fatalf("ReadFile %s: %v", path, err) log.Fatalf("ReadFile %s: %v", path, err)
} }
@ -742,9 +741,9 @@ func extractString(src []byte, rx *regexp.Regexp) (s string) {
func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) { func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
// get HTML body contents // get HTML body contents
src, err := ioutil.ReadFile(abspath) src, err := fs.ReadFile(abspath)
if err != nil { if err != nil {
log.Printf("ioutil.ReadFile: %s", err) log.Printf("ReadFile: %s", err)
serveError(w, r, relpath, err) serveError(w, r, relpath, err)
return return
} }
@ -793,9 +792,9 @@ func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
} }
func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) { func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
src, err := ioutil.ReadFile(abspath) src, err := fs.ReadFile(abspath)
if err != nil { if err != nil {
log.Printf("ioutil.ReadFile: %s", err) log.Printf("ReadFile: %s", err)
serveError(w, r, relpath, err) serveError(w, r, relpath, err)
return return
} }
@ -814,19 +813,13 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
return return
} }
list, err := ioutil.ReadDir(abspath) list, err := fs.ReadDir(abspath)
if err != nil { if err != nil {
log.Printf("ioutil.ReadDir: %s", err) log.Printf("ReadDir: %s", err)
serveError(w, r, relpath, err) serveError(w, r, relpath, err)
return return
} }
for _, d := range list {
if d.IsDirectory() {
d.Size = 0
}
}
contents := applyTemplate(dirlistHTML, "dirlistHTML", list) contents := applyTemplate(dirlistHTML, "dirlistHTML", list)
servePage(w, "Directory "+relpath, "", "", contents) servePage(w, "Directory "+relpath, "", "", contents)
} }
@ -864,7 +857,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
return return
} }
dir, err := os.Lstat(abspath) dir, err := fs.Lstat(abspath)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
serveError(w, r, relpath, err) serveError(w, r, relpath, err)
@ -942,15 +935,15 @@ type httpHandler struct {
// //
func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo { func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo {
// filter function to select the desired .go files // filter function to select the desired .go files
filter := func(d *os.FileInfo) bool { filter := func(d FileInfo) bool {
// If we are looking at cmd documentation, only accept // If we are looking at cmd documentation, only accept
// the special fakePkgFile containing the documentation. // the special fakePkgFile containing the documentation.
return isPkgFile(d) && (h.isPkg || d.Name == fakePkgFile) return isPkgFile(d) && (h.isPkg || d.Name() == fakePkgFile)
} }
// get package ASTs // get package ASTs
fset := token.NewFileSet() fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, abspath, filter, parser.ParseComments) pkgs, err := parseDir(fset, abspath, filter)
if err != nil && pkgs == nil { if err != nil && pkgs == nil {
// only report directory read errors, ignore parse errors // only report directory read errors, ignore parse errors
// (may be able to extract partial package information) // (may be able to extract partial package information)

View File

@ -45,7 +45,6 @@ import (
"go/token" "go/token"
"go/scanner" "go/scanner"
"index/suffixarray" "index/suffixarray"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -624,7 +623,7 @@ func pkgName(filename string) string {
// failed (that is, if the file was not added), it returns file == nil. // 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) { func (x *Indexer) addFile(filename string, goFile bool) (file *token.File, ast *ast.File) {
// open file // open file
f, err := os.Open(filename) f, err := fs.Open(filename)
if err != nil { if err != nil {
return return
} }
@ -727,12 +726,12 @@ func isWhitelisted(filename string) bool {
} }
func (x *Indexer) visitFile(dirname string, f *os.FileInfo, fulltextIndex bool) { func (x *Indexer) visitFile(dirname string, f FileInfo, fulltextIndex bool) {
if !f.IsRegular() { if !f.IsRegular() {
return return
} }
filename := filepath.Join(dirname, f.Name) filename := filepath.Join(dirname, f.Name())
goFile := false goFile := false
switch { switch {
@ -745,7 +744,7 @@ func (x *Indexer) visitFile(dirname string, f *os.FileInfo, fulltextIndex bool)
} }
goFile = true goFile = true
case !fulltextIndex || !isWhitelisted(f.Name): case !fulltextIndex || !isWhitelisted(f.Name()):
return return
} }
@ -804,7 +803,7 @@ func NewIndex(dirnames <-chan string, fulltextIndex bool) *Index {
// index all files in the directories given by dirnames // index all files in the directories given by dirnames
for dirname := range dirnames { for dirname := range dirnames {
list, err := ioutil.ReadDir(dirname) list, err := fs.ReadDir(dirname)
if err != nil { if err != nil {
continue // ignore this directory continue // ignore this directory
} }

View File

@ -222,6 +222,10 @@ func main() {
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
// Determine file system to use.
// TODO(gri) Complete this - for now we only have one.
fs = OS
// Clean goroot: normalize path separator. // Clean goroot: normalize path separator.
*goroot = filepath.Clean(*goroot) *goroot = filepath.Clean(*goroot)

View File

@ -9,7 +9,6 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"os"
"path" "path"
"path/filepath" "path/filepath"
"sort" "sort"
@ -174,7 +173,7 @@ func (m *Mapping) ToAbsolute(spath string) string {
continue // no match continue // no match
} }
abspath := filepath.Join(e.path, tail) abspath := filepath.Join(e.path, tail)
if _, err := os.Stat(abspath); err == nil { if _, err := fs.Stat(abspath); err == nil {
return abspath return abspath
} }
} }

69
src/cmd/godoc/parser.go Normal file
View File

@ -0,0 +1,69 @@
// 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 contains support functions for parsing .go files.
// Similar functionality is found in package go/parser but the
// functions here operate using godoc's file system fs instead
// of calling the OS's file operations directly.
package main
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
)
func parseFiles(fset *token.FileSet, filenames []string) (pkgs map[string]*ast.Package, first os.Error) {
pkgs = make(map[string]*ast.Package)
for _, filename := range filenames {
src, err := fs.ReadFile(filename)
if err != nil {
if first == nil {
first = err
}
continue
}
file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
if err != nil {
if first == nil {
first = err
}
continue
}
name := file.Name.Name
pkg, found := pkgs[name]
if !found {
// TODO(gri) Use NewPackage here; reconsider ParseFiles API.
pkg = &ast.Package{name, nil, nil, make(map[string]*ast.File)}
pkgs[name] = pkg
}
pkg.Files[filename] = file
}
return
}
func parseDir(fset *token.FileSet, path string, filter func(FileInfo) bool) (map[string]*ast.Package, os.Error) {
list, err := fs.ReadDir(path)
if err != nil {
return nil, err
}
filenames := make([]string, len(list))
i := 0
for _, d := range list {
if filter == nil || filter(d) {
filenames[i] = filepath.Join(path, d.Name())
i++
}
}
filenames = filenames[0:i]
return parseFiles(fset, filenames)
}

View File

@ -44,6 +44,10 @@ func (v *RWValue) get() (interface{}, int64) {
} }
// TODO(gri) For now, using os.Getwd() is ok here since the functionality
// based on this code is not invoked for the appengine version,
// but this is fragile. Determine what the right thing to do is,
// here (possibly have some Getwd-equivalent in FileSystem).
var cwd, _ = os.Getwd() // ignore errors var cwd, _ = os.Getwd() // ignore errors
// canonicalizePaths takes a list of (directory/file) paths and returns // canonicalizePaths takes a list of (directory/file) paths and returns
@ -95,6 +99,7 @@ func canonicalizePaths(list []string, filter func(path string) bool) []string {
// atomically renames that file to the file named by filename. // atomically renames that file to the file named by filename.
// //
func writeFileAtomically(filename string, data []byte) os.Error { func writeFileAtomically(filename string, data []byte) os.Error {
// TODO(gri) this won't work on appengine
f, err := ioutil.TempFile(filepath.Split(filename)) f, err := ioutil.TempFile(filepath.Split(filename))
if err != nil { if err != nil {
return err return err
@ -155,7 +160,7 @@ func isTextFile(filename string) bool {
// the extension is not known; read an initial chunk // the extension is not known; read an initial chunk
// of the file and check if it looks like text // of the file and check if it looks like text
f, err := os.Open(filename) f, err := fs.Open(filename)
if err != nil { if err != nil {
return false return false
} }