go.tools/godoc: move vfs code to vfs package
R=bradfitz CC=golang-dev https://golang.org/cl/11414043
This commit is contained in:
parent
7526441b70
commit
5ff0687cc9
|
|
@ -9,16 +9,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
pathpkg "path"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.google.com/p/go.tools/godoc/vfs"
|
"code.google.com/p/go.tools/godoc/vfs"
|
||||||
)
|
)
|
||||||
|
|
@ -36,32 +27,18 @@ import (
|
||||||
// of the name space and then bind any GOPATH/src directories
|
// of the name space and then bind any GOPATH/src directories
|
||||||
// on top of /src/pkg, so that all sources are in /src/pkg.
|
// on top of /src/pkg, so that all sources are in /src/pkg.
|
||||||
//
|
//
|
||||||
// For more about name spaces, see the nameSpace type's
|
// For more about name spaces, see the NameSpace type's
|
||||||
// documentation below.
|
// documentation in code.google.com/p/go.tools/godoc/vfs.
|
||||||
//
|
//
|
||||||
// The use of this virtual file system means that most code processing
|
// The use of this virtual file system means that most code processing
|
||||||
// paths can assume they are slash-separated and should be using
|
// paths can assume they are slash-separated and should be using
|
||||||
// package path (often imported as pathpkg) to manipulate them,
|
// package path (often imported as pathpkg) to manipulate them,
|
||||||
// even on Windows.
|
// even on Windows.
|
||||||
//
|
//
|
||||||
var fs = nameSpace{} // the underlying file system for godoc
|
var fs = vfs.NameSpace{} // the underlying file system for godoc
|
||||||
|
|
||||||
// Setting debugNS = true will enable debugging prints about
|
|
||||||
// name space translations.
|
|
||||||
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) (vfs.ReadSeekCloser, error)
|
|
||||||
Lstat(path string) (os.FileInfo, error)
|
|
||||||
Stat(path string) (os.FileInfo, error)
|
|
||||||
ReadDir(path string) ([]os.FileInfo, error)
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 vfs.FileSystem, path string) ([]byte, error) {
|
||||||
rc, err := fs.Open(path)
|
rc, err := fs.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -69,490 +46,3 @@ func ReadFile(fs FileSystem, path string) ([]byte, error) {
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
return ioutil.ReadAll(rc)
|
return ioutil.ReadAll(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OS returns an implementation of FileSystem reading from the
|
|
||||||
// tree rooted at root. Recording a root is convenient everywhere
|
|
||||||
// but necessary on Windows, because the slash-separated path
|
|
||||||
// passed to Open has no way to specify a drive letter. Using a root
|
|
||||||
// lets code refer to OS(`c:\`), OS(`d:\`) and so on.
|
|
||||||
func OS(root string) FileSystem {
|
|
||||||
return osFS(root)
|
|
||||||
}
|
|
||||||
|
|
||||||
type osFS string
|
|
||||||
|
|
||||||
func (root osFS) String() string { return "os(" + string(root) + ")" }
|
|
||||||
|
|
||||||
func (root osFS) resolve(path string) string {
|
|
||||||
// Clean the path so that it cannot possibly begin with ../.
|
|
||||||
// If it did, the result of filepath.Join would be outside the
|
|
||||||
// tree rooted at root. We probably won't ever see a path
|
|
||||||
// with .. in it, but be safe anyway.
|
|
||||||
path = pathpkg.Clean("/" + path)
|
|
||||||
|
|
||||||
return filepath.Join(string(root), path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (root osFS) Open(path string) (vfs.ReadSeekCloser, error) {
|
|
||||||
f, err := os.Open(root.resolve(path))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if fi.IsDir() {
|
|
||||||
return nil, fmt.Errorf("Open: %s is a directory", path)
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (root osFS) Lstat(path string) (os.FileInfo, error) {
|
|
||||||
return os.Lstat(root.resolve(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (root osFS) Stat(path string) (os.FileInfo, error) {
|
|
||||||
return os.Stat(root.resolve(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (root osFS) ReadDir(path string) ([]os.FileInfo, error) {
|
|
||||||
return ioutil.ReadDir(root.resolve(path)) // is sorted
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasPathPrefix returns true if x == y or x == y + "/" + more
|
|
||||||
func hasPathPrefix(x, y string) bool {
|
|
||||||
return x == y || strings.HasPrefix(x, y) && (strings.HasSuffix(y, "/") || strings.HasPrefix(x[len(y):], "/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// A nameSpace is a file system made up of other file systems
|
|
||||||
// mounted at specific locations in the name space.
|
|
||||||
//
|
|
||||||
// The representation is a map from mount point locations
|
|
||||||
// to the list of file systems mounted at that location. A traditional
|
|
||||||
// Unix mount table would use a single file system per mount point,
|
|
||||||
// but we want to be able to mount multiple file systems on a single
|
|
||||||
// mount point and have the system behave as if the union of those
|
|
||||||
// file systems were present at the mount point.
|
|
||||||
// For example, if the OS file system has a Go installation in
|
|
||||||
// c:\Go and additional Go path trees in d:\Work1 and d:\Work2, then
|
|
||||||
// this name space creates the view we want for the godoc server:
|
|
||||||
//
|
|
||||||
// nameSpace{
|
|
||||||
// "/": {
|
|
||||||
// {old: "/", fs: OS(`c:\Go`), new: "/"},
|
|
||||||
// },
|
|
||||||
// "/src/pkg": {
|
|
||||||
// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
|
|
||||||
// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
|
|
||||||
// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This is created by executing:
|
|
||||||
//
|
|
||||||
// ns := nameSpace{}
|
|
||||||
// ns.Bind("/", OS(`c:\Go`), "/", bindReplace)
|
|
||||||
// ns.Bind("/src/pkg", OS(`d:\Work1`), "/src", bindAfter)
|
|
||||||
// ns.Bind("/src/pkg", OS(`d:\Work2`), "/src", bindAfter)
|
|
||||||
//
|
|
||||||
// A particular mount point entry is a triple (old, fs, new), meaning that to
|
|
||||||
// operate on a path beginning with old, replace that prefix (old) with new
|
|
||||||
// and then pass that path to the FileSystem implementation fs.
|
|
||||||
//
|
|
||||||
// Given this name space, a ReadDir of /src/pkg/code will check each prefix
|
|
||||||
// of the path for a mount point (first /src/pkg/code, then /src/pkg, then /src,
|
|
||||||
// then /), stopping when it finds one. For the above example, /src/pkg/code
|
|
||||||
// will find the mount point at /src/pkg:
|
|
||||||
//
|
|
||||||
// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
|
|
||||||
// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
|
|
||||||
// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
|
|
||||||
//
|
|
||||||
// ReadDir will when execute these three calls and merge the results:
|
|
||||||
//
|
|
||||||
// OS(`c:\Go`).ReadDir("/src/pkg/code")
|
|
||||||
// OS(`d:\Work1').ReadDir("/src/code")
|
|
||||||
// OS(`d:\Work2').ReadDir("/src/code")
|
|
||||||
//
|
|
||||||
// Note that the "/src/pkg" in "/src/pkg/code" has been replaced by
|
|
||||||
// just "/src" in the final two calls.
|
|
||||||
//
|
|
||||||
// OS is itself an implementation of a file system: it implements
|
|
||||||
// OS(`c:\Go`).ReadDir("/src/pkg/code") as ioutil.ReadDir(`c:\Go\src\pkg\code`).
|
|
||||||
//
|
|
||||||
// Because the new path is evaluated by fs (here OS(root)), another way
|
|
||||||
// to read the mount table is to mentally combine fs+new, so that this table:
|
|
||||||
//
|
|
||||||
// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
|
|
||||||
// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
|
|
||||||
// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
|
|
||||||
//
|
|
||||||
// reads as:
|
|
||||||
//
|
|
||||||
// "/src/pkg" -> c:\Go\src\pkg
|
|
||||||
// "/src/pkg" -> d:\Work1\src
|
|
||||||
// "/src/pkg" -> d:\Work2\src
|
|
||||||
//
|
|
||||||
// An invariant (a redundancy) of the name space representation is that
|
|
||||||
// ns[mtpt][i].old is always equal to mtpt (in the example, ns["/src/pkg"]'s
|
|
||||||
// mount table entries always have old == "/src/pkg"). The 'old' field is
|
|
||||||
// useful to callers, because they receive just a []mountedFS and not any
|
|
||||||
// other indication of which mount point was found.
|
|
||||||
//
|
|
||||||
type nameSpace map[string][]mountedFS
|
|
||||||
|
|
||||||
// A mountedFS handles requests for path by replacing
|
|
||||||
// a prefix 'old' with 'new' and then calling the fs methods.
|
|
||||||
type mountedFS struct {
|
|
||||||
old string
|
|
||||||
fs FileSystem
|
|
||||||
new string
|
|
||||||
}
|
|
||||||
|
|
||||||
// translate translates path for use in m, replacing old with new.
|
|
||||||
//
|
|
||||||
// mountedFS{"/src/pkg", fs, "/src"}.translate("/src/pkg/code") == "/src/code".
|
|
||||||
func (m mountedFS) translate(path string) string {
|
|
||||||
path = pathpkg.Clean("/" + path)
|
|
||||||
if !hasPathPrefix(path, m.old) {
|
|
||||||
panic("translate " + path + " but old=" + m.old)
|
|
||||||
}
|
|
||||||
return pathpkg.Join(m.new, path[len(m.old):])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nameSpace) String() string {
|
|
||||||
return "ns"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint writes a text representation of the name space to w.
|
|
||||||
func (ns nameSpace) Fprint(w io.Writer) {
|
|
||||||
fmt.Fprint(w, "name space {\n")
|
|
||||||
var all []string
|
|
||||||
for mtpt := range ns {
|
|
||||||
all = append(all, mtpt)
|
|
||||||
}
|
|
||||||
sort.Strings(all)
|
|
||||||
for _, mtpt := range all {
|
|
||||||
fmt.Fprintf(w, "\t%s:\n", mtpt)
|
|
||||||
for _, m := range ns[mtpt] {
|
|
||||||
fmt.Fprintf(w, "\t\t%s %s\n", m.fs, m.new)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, "}\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean returns a cleaned, rooted path for evaluation.
|
|
||||||
// It canonicalizes the path so that we can use string operations
|
|
||||||
// to analyze it.
|
|
||||||
func (nameSpace) clean(path string) string {
|
|
||||||
return pathpkg.Clean("/" + path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind causes references to old to redirect to the path new in newfs.
|
|
||||||
// If mode is bindReplace, old redirections are discarded.
|
|
||||||
// If mode is bindBefore, this redirection takes priority over existing ones,
|
|
||||||
// but earlier ones are still consulted for paths that do not exist in newfs.
|
|
||||||
// If mode is bindAfter, this redirection happens only after existing ones
|
|
||||||
// have been tried and failed.
|
|
||||||
|
|
||||||
const (
|
|
||||||
bindReplace = iota
|
|
||||||
bindBefore
|
|
||||||
bindAfter
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ns nameSpace) Bind(old string, newfs FileSystem, new string, mode int) {
|
|
||||||
old = ns.clean(old)
|
|
||||||
new = ns.clean(new)
|
|
||||||
m := mountedFS{old, newfs, new}
|
|
||||||
var mtpt []mountedFS
|
|
||||||
switch mode {
|
|
||||||
case bindReplace:
|
|
||||||
mtpt = append(mtpt, m)
|
|
||||||
case bindAfter:
|
|
||||||
mtpt = append(mtpt, ns.resolve(old)...)
|
|
||||||
mtpt = append(mtpt, m)
|
|
||||||
case bindBefore:
|
|
||||||
mtpt = append(mtpt, m)
|
|
||||||
mtpt = append(mtpt, ns.resolve(old)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend m.old, m.new in inherited mount point entries.
|
|
||||||
for i := range mtpt {
|
|
||||||
m := &mtpt[i]
|
|
||||||
if m.old != old {
|
|
||||||
if !hasPathPrefix(old, m.old) {
|
|
||||||
// This should not happen. If it does, panic so
|
|
||||||
// that we can see the call trace that led to it.
|
|
||||||
panic(fmt.Sprintf("invalid Bind: old=%q m={%q, %s, %q}", old, m.old, m.fs.String(), m.new))
|
|
||||||
}
|
|
||||||
suffix := old[len(m.old):]
|
|
||||||
m.old = pathpkg.Join(m.old, suffix)
|
|
||||||
m.new = pathpkg.Join(m.new, suffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ns[old] = mtpt
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve resolves a path to the list of mountedFS to use for path.
|
|
||||||
func (ns nameSpace) resolve(path string) []mountedFS {
|
|
||||||
path = ns.clean(path)
|
|
||||||
for {
|
|
||||||
if m := ns[path]; m != nil {
|
|
||||||
if debugNS {
|
|
||||||
fmt.Printf("resolve %s: %v\n", path, m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
if path == "/" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
path = pathpkg.Dir(path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open implements the FileSystem Open method.
|
|
||||||
func (ns nameSpace) Open(path string) (vfs.ReadSeekCloser, error) {
|
|
||||||
var err error
|
|
||||||
for _, m := range ns.resolve(path) {
|
|
||||||
if debugNS {
|
|
||||||
fmt.Printf("tx %s: %v\n", path, m.translate(path))
|
|
||||||
}
|
|
||||||
r, err1 := m.fs.Open(m.translate(path))
|
|
||||||
if err1 == nil {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// stat implements the FileSystem Stat and Lstat methods.
|
|
||||||
func (ns nameSpace) stat(path string, f func(FileSystem, string) (os.FileInfo, error)) (os.FileInfo, error) {
|
|
||||||
var err error
|
|
||||||
for _, m := range ns.resolve(path) {
|
|
||||||
fi, err1 := f(m.fs, m.translate(path))
|
|
||||||
if err1 == nil {
|
|
||||||
return fi, nil
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = &os.PathError{Op: "stat", Path: path, Err: os.ErrNotExist}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns nameSpace) Stat(path string) (os.FileInfo, error) {
|
|
||||||
return ns.stat(path, FileSystem.Stat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns nameSpace) Lstat(path string) (os.FileInfo, error) {
|
|
||||||
return ns.stat(path, FileSystem.Lstat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dirInfo is a trivial implementation of os.FileInfo for a directory.
|
|
||||||
type dirInfo string
|
|
||||||
|
|
||||||
func (d dirInfo) Name() string { return string(d) }
|
|
||||||
func (d dirInfo) Size() int64 { return 0 }
|
|
||||||
func (d dirInfo) Mode() os.FileMode { return os.ModeDir | 0555 }
|
|
||||||
func (d dirInfo) ModTime() time.Time { return startTime }
|
|
||||||
func (d dirInfo) IsDir() bool { return true }
|
|
||||||
func (d dirInfo) Sys() interface{} { return nil }
|
|
||||||
|
|
||||||
var startTime = time.Now()
|
|
||||||
|
|
||||||
// ReadDir implements the FileSystem ReadDir method. It's where most of the magic is.
|
|
||||||
// (The rest is in resolve.)
|
|
||||||
//
|
|
||||||
// Logically, ReadDir must return the union of all the directories that are named
|
|
||||||
// by path. In order to avoid misinterpreting Go packages, of all the directories
|
|
||||||
// that contain Go source code, we only include the files from the first,
|
|
||||||
// but we include subdirectories from all.
|
|
||||||
//
|
|
||||||
// ReadDir must also return directory entries needed to reach mount points.
|
|
||||||
// If the name space looks like the example in the type nameSpace comment,
|
|
||||||
// but c:\Go does not have a src/pkg subdirectory, we still want to be able
|
|
||||||
// to find that subdirectory, because we've mounted d:\Work1 and d:\Work2
|
|
||||||
// there. So if we don't see "src" in the directory listing for c:\Go, we add an
|
|
||||||
// entry for it before returning.
|
|
||||||
//
|
|
||||||
func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
|
|
||||||
path = ns.clean(path)
|
|
||||||
|
|
||||||
var (
|
|
||||||
haveGo = false
|
|
||||||
haveName = map[string]bool{}
|
|
||||||
all []os.FileInfo
|
|
||||||
err error
|
|
||||||
first []os.FileInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, m := range ns.resolve(path) {
|
|
||||||
dir, err1 := m.fs.ReadDir(m.translate(path))
|
|
||||||
if err1 != nil {
|
|
||||||
if err == nil {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir == nil {
|
|
||||||
dir = []os.FileInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if first == nil {
|
|
||||||
first = dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't yet have Go files in 'all' and this directory
|
|
||||||
// has some, add all the files from this directory.
|
|
||||||
// Otherwise, only add subdirectories.
|
|
||||||
useFiles := false
|
|
||||||
if !haveGo {
|
|
||||||
for _, d := range dir {
|
|
||||||
if strings.HasSuffix(d.Name(), ".go") {
|
|
||||||
useFiles = true
|
|
||||||
haveGo = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range dir {
|
|
||||||
name := d.Name()
|
|
||||||
if (d.IsDir() || useFiles) && !haveName[name] {
|
|
||||||
haveName[name] = true
|
|
||||||
all = append(all, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't find any directories containing Go files.
|
|
||||||
// If some directory returned successfully, use that.
|
|
||||||
if !haveGo {
|
|
||||||
for _, d := range first {
|
|
||||||
if !haveName[d.Name()] {
|
|
||||||
haveName[d.Name()] = true
|
|
||||||
all = append(all, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Built union. Add any missing directories needed to reach mount points.
|
|
||||||
for old := range ns {
|
|
||||||
if hasPathPrefix(old, path) && old != path {
|
|
||||||
// Find next element after path in old.
|
|
||||||
elem := old[len(path):]
|
|
||||||
elem = strings.TrimPrefix(elem, "/")
|
|
||||||
if i := strings.Index(elem, "/"); i >= 0 {
|
|
||||||
elem = elem[:i]
|
|
||||||
}
|
|
||||||
if !haveName[elem] {
|
|
||||||
haveName[elem] = true
|
|
||||||
all = append(all, dirInfo(elem))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(all) == 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(byName(all))
|
|
||||||
return all, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// byName implements sort.Interface.
|
|
||||||
type byName []os.FileInfo
|
|
||||||
|
|
||||||
func (f byName) Len() int { return len(f) }
|
|
||||||
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
|
||||||
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
||||||
|
|
||||||
// An httpFS implements http.FileSystem using a FileSystem.
|
|
||||||
type httpFS struct {
|
|
||||||
fs FileSystem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpFS) Open(name string) (http.File, error) {
|
|
||||||
fi, err := h.fs.Stat(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if fi.IsDir() {
|
|
||||||
return &httpDir{h.fs, name, nil}, nil
|
|
||||||
}
|
|
||||||
f, err := h.fs.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &httpFile{h.fs, f, name}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpDir implements http.File for a directory in a FileSystem.
|
|
||||||
type httpDir struct {
|
|
||||||
fs FileSystem
|
|
||||||
name string
|
|
||||||
pending []os.FileInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpDir) Close() error { return nil }
|
|
||||||
func (h *httpDir) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) }
|
|
||||||
func (h *httpDir) Read([]byte) (int, error) {
|
|
||||||
return 0, fmt.Errorf("cannot Read from directory %s", h.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpDir) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
if offset == 0 && whence == 0 {
|
|
||||||
h.pending = nil
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("unsupported Seek in directory %s", h.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpDir) Readdir(count int) ([]os.FileInfo, error) {
|
|
||||||
if h.pending == nil {
|
|
||||||
d, err := h.fs.ReadDir(h.name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if d == nil {
|
|
||||||
d = []os.FileInfo{} // not nil
|
|
||||||
}
|
|
||||||
h.pending = d
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(h.pending) == 0 && count > 0 {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
if count <= 0 || count > len(h.pending) {
|
|
||||||
count = len(h.pending)
|
|
||||||
}
|
|
||||||
d := h.pending[:count]
|
|
||||||
h.pending = h.pending[count:]
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpFile implements http.File for a file (not directory) in a FileSystem.
|
|
||||||
type httpFile struct {
|
|
||||||
fs FileSystem
|
|
||||||
vfs.ReadSeekCloser
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpFile) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) }
|
|
||||||
func (h *httpFile) Readdir(int) ([]os.FileInfo, error) {
|
|
||||||
return nil, fmt.Errorf("cannot Readdir from file %s", h.name)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/godoc/util"
|
"code.google.com/p/go.tools/godoc/util"
|
||||||
|
"code.google.com/p/go.tools/godoc/vfs/httpfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
@ -77,7 +78,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func initHandlers() {
|
func initHandlers() {
|
||||||
fileServer = http.FileServer(&httpFS{fs})
|
fileServer = http.FileServer(httpfs.New(fs))
|
||||||
cmdHandler = docServer{"/cmd/", "/src/cmd"}
|
cmdHandler = docServer{"/cmd/", "/src/cmd"}
|
||||||
pkgHandler = docServer{"/pkg/", "/src/pkg"}
|
pkgHandler = docServer{"/pkg/", "/src/pkg"}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/godoc/vfs"
|
||||||
|
"code.google.com/p/go.tools/godoc/vfs/zipfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultAddr = ":6060" // default webserver address
|
const defaultAddr = ":6060" // default webserver address
|
||||||
|
|
@ -172,9 +175,9 @@ func main() {
|
||||||
// same is true for the http handlers in initHandlers.
|
// same is true for the http handlers in initHandlers.
|
||||||
if *zipfile == "" {
|
if *zipfile == "" {
|
||||||
// use file system of underlying OS
|
// use file system of underlying OS
|
||||||
fs.Bind("/", OS(*goroot), "/", bindReplace)
|
fs.Bind("/", vfs.OS(*goroot), "/", vfs.BindReplace)
|
||||||
if *templateDir != "" {
|
if *templateDir != "" {
|
||||||
fs.Bind("/lib/godoc", OS(*templateDir), "/", bindBefore)
|
fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// use file system specified via .zip file (path separator must be '/')
|
// use file system specified via .zip file (path separator must be '/')
|
||||||
|
|
@ -183,12 +186,12 @@ func main() {
|
||||||
log.Fatalf("%s: %s\n", *zipfile, err)
|
log.Fatalf("%s: %s\n", *zipfile, err)
|
||||||
}
|
}
|
||||||
defer rc.Close() // be nice (e.g., -writeIndex mode)
|
defer rc.Close() // be nice (e.g., -writeIndex mode)
|
||||||
fs.Bind("/", NewZipFS(rc, *zipfile), *goroot, bindReplace)
|
fs.Bind("/", zipvfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind $GOPATH trees into Go root.
|
// Bind $GOPATH trees into Go root.
|
||||||
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
||||||
fs.Bind("/src/pkg", OS(p), "/src", bindAfter)
|
fs.Bind("/src/pkg", vfs.OS(p), "/src", vfs.BindAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
readTemplates()
|
readTemplates()
|
||||||
|
|
@ -339,18 +342,18 @@ func main() {
|
||||||
var forceCmd bool
|
var forceCmd bool
|
||||||
var abspath, relpath string
|
var abspath, relpath string
|
||||||
if filepath.IsAbs(path) {
|
if filepath.IsAbs(path) {
|
||||||
fs.Bind(target, OS(path), "/", bindReplace)
|
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
|
||||||
abspath = target
|
abspath = target
|
||||||
} else if build.IsLocalImport(path) {
|
} else if build.IsLocalImport(path) {
|
||||||
cwd, _ := os.Getwd() // ignore errors
|
cwd, _ := os.Getwd() // ignore errors
|
||||||
path = filepath.Join(cwd, path)
|
path = filepath.Join(cwd, path)
|
||||||
fs.Bind(target, OS(path), "/", bindReplace)
|
fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace)
|
||||||
abspath = target
|
abspath = target
|
||||||
} else if strings.HasPrefix(path, cmdPrefix) {
|
} else if strings.HasPrefix(path, cmdPrefix) {
|
||||||
path = strings.TrimPrefix(path, cmdPrefix)
|
path = strings.TrimPrefix(path, cmdPrefix)
|
||||||
forceCmd = true
|
forceCmd = true
|
||||||
} else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
|
} else if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
|
||||||
fs.Bind(target, OS(bp.Dir), "/", bindReplace)
|
fs.Bind(target, vfs.OS(bp.Dir), "/", vfs.BindReplace)
|
||||||
abspath = target
|
abspath = target
|
||||||
relpath = bp.ImportPath
|
relpath = bp.ImportPath
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
// 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 httpfs implements http.FileSystem using a godoc vfs.FileSystem.
|
||||||
|
package httpfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/godoc/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(fs vfs.FileSystem) http.FileSystem {
|
||||||
|
return &httpFS{fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpFS struct {
|
||||||
|
fs vfs.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpFS) Open(name string) (http.File, error) {
|
||||||
|
fi, err := h.fs.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
return &httpDir{h.fs, name, nil}, nil
|
||||||
|
}
|
||||||
|
f, err := h.fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &httpFile{h.fs, f, name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpDir implements http.File for a directory in a FileSystem.
|
||||||
|
type httpDir struct {
|
||||||
|
fs vfs.FileSystem
|
||||||
|
name string
|
||||||
|
pending []os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpDir) Close() error { return nil }
|
||||||
|
func (h *httpDir) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) }
|
||||||
|
func (h *httpDir) Read([]byte) (int, error) {
|
||||||
|
return 0, fmt.Errorf("cannot Read from directory %s", h.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpDir) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if offset == 0 && whence == 0 {
|
||||||
|
h.pending = nil
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("unsupported Seek in directory %s", h.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpDir) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
if h.pending == nil {
|
||||||
|
d, err := h.fs.ReadDir(h.name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if d == nil {
|
||||||
|
d = []os.FileInfo{} // not nil
|
||||||
|
}
|
||||||
|
h.pending = d
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(h.pending) == 0 && count > 0 {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
if count <= 0 || count > len(h.pending) {
|
||||||
|
count = len(h.pending)
|
||||||
|
}
|
||||||
|
d := h.pending[:count]
|
||||||
|
h.pending = h.pending[count:]
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpFile implements http.File for a file (not directory) in a FileSystem.
|
||||||
|
type httpFile struct {
|
||||||
|
fs vfs.FileSystem
|
||||||
|
vfs.ReadSeekCloser
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpFile) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) }
|
||||||
|
func (h *httpFile) Readdir(int) ([]os.FileInfo, error) {
|
||||||
|
return nil, fmt.Errorf("cannot Readdir from file %s", h.name)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,381 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
pathpkg "path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setting debugNS = true will enable debugging prints about
|
||||||
|
// name space translations.
|
||||||
|
const debugNS = false
|
||||||
|
|
||||||
|
// A NameSpace is a file system made up of other file systems
|
||||||
|
// mounted at specific locations in the name space.
|
||||||
|
//
|
||||||
|
// The representation is a map from mount point locations
|
||||||
|
// to the list of file systems mounted at that location. A traditional
|
||||||
|
// Unix mount table would use a single file system per mount point,
|
||||||
|
// but we want to be able to mount multiple file systems on a single
|
||||||
|
// mount point and have the system behave as if the union of those
|
||||||
|
// file systems were present at the mount point.
|
||||||
|
// For example, if the OS file system has a Go installation in
|
||||||
|
// c:\Go and additional Go path trees in d:\Work1 and d:\Work2, then
|
||||||
|
// this name space creates the view we want for the godoc server:
|
||||||
|
//
|
||||||
|
// NameSpace{
|
||||||
|
// "/": {
|
||||||
|
// {old: "/", fs: OS(`c:\Go`), new: "/"},
|
||||||
|
// },
|
||||||
|
// "/src/pkg": {
|
||||||
|
// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
|
||||||
|
// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
|
||||||
|
// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This is created by executing:
|
||||||
|
//
|
||||||
|
// ns := NameSpace{}
|
||||||
|
// ns.Bind("/", OS(`c:\Go`), "/", BindReplace)
|
||||||
|
// ns.Bind("/src/pkg", OS(`d:\Work1`), "/src", BindAfter)
|
||||||
|
// ns.Bind("/src/pkg", OS(`d:\Work2`), "/src", BindAfter)
|
||||||
|
//
|
||||||
|
// A particular mount point entry is a triple (old, fs, new), meaning that to
|
||||||
|
// operate on a path beginning with old, replace that prefix (old) with new
|
||||||
|
// and then pass that path to the FileSystem implementation fs.
|
||||||
|
//
|
||||||
|
// Given this name space, a ReadDir of /src/pkg/code will check each prefix
|
||||||
|
// of the path for a mount point (first /src/pkg/code, then /src/pkg, then /src,
|
||||||
|
// then /), stopping when it finds one. For the above example, /src/pkg/code
|
||||||
|
// will find the mount point at /src/pkg:
|
||||||
|
//
|
||||||
|
// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
|
||||||
|
// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
|
||||||
|
// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
|
||||||
|
//
|
||||||
|
// ReadDir will when execute these three calls and merge the results:
|
||||||
|
//
|
||||||
|
// OS(`c:\Go`).ReadDir("/src/pkg/code")
|
||||||
|
// OS(`d:\Work1').ReadDir("/src/code")
|
||||||
|
// OS(`d:\Work2').ReadDir("/src/code")
|
||||||
|
//
|
||||||
|
// Note that the "/src/pkg" in "/src/pkg/code" has been replaced by
|
||||||
|
// just "/src" in the final two calls.
|
||||||
|
//
|
||||||
|
// OS is itself an implementation of a file system: it implements
|
||||||
|
// OS(`c:\Go`).ReadDir("/src/pkg/code") as ioutil.ReadDir(`c:\Go\src\pkg\code`).
|
||||||
|
//
|
||||||
|
// Because the new path is evaluated by fs (here OS(root)), another way
|
||||||
|
// to read the mount table is to mentally combine fs+new, so that this table:
|
||||||
|
//
|
||||||
|
// {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
|
||||||
|
// {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
|
||||||
|
// {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
|
||||||
|
//
|
||||||
|
// reads as:
|
||||||
|
//
|
||||||
|
// "/src/pkg" -> c:\Go\src\pkg
|
||||||
|
// "/src/pkg" -> d:\Work1\src
|
||||||
|
// "/src/pkg" -> d:\Work2\src
|
||||||
|
//
|
||||||
|
// An invariant (a redundancy) of the name space representation is that
|
||||||
|
// ns[mtpt][i].old is always equal to mtpt (in the example, ns["/src/pkg"]'s
|
||||||
|
// mount table entries always have old == "/src/pkg"). The 'old' field is
|
||||||
|
// useful to callers, because they receive just a []mountedFS and not any
|
||||||
|
// other indication of which mount point was found.
|
||||||
|
//
|
||||||
|
type NameSpace map[string][]mountedFS
|
||||||
|
|
||||||
|
// A mountedFS handles requests for path by replacing
|
||||||
|
// a prefix 'old' with 'new' and then calling the fs methods.
|
||||||
|
type mountedFS struct {
|
||||||
|
old string
|
||||||
|
fs FileSystem
|
||||||
|
new string
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasPathPrefix returns true if x == y or x == y + "/" + more
|
||||||
|
func hasPathPrefix(x, y string) bool {
|
||||||
|
return x == y || strings.HasPrefix(x, y) && (strings.HasSuffix(y, "/") || strings.HasPrefix(x[len(y):], "/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// translate translates path for use in m, replacing old with new.
|
||||||
|
//
|
||||||
|
// mountedFS{"/src/pkg", fs, "/src"}.translate("/src/pkg/code") == "/src/code".
|
||||||
|
func (m mountedFS) translate(path string) string {
|
||||||
|
path = pathpkg.Clean("/" + path)
|
||||||
|
if !hasPathPrefix(path, m.old) {
|
||||||
|
panic("translate " + path + " but old=" + m.old)
|
||||||
|
}
|
||||||
|
return pathpkg.Join(m.new, path[len(m.old):])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NameSpace) String() string {
|
||||||
|
return "ns"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint writes a text representation of the name space to w.
|
||||||
|
func (ns NameSpace) Fprint(w io.Writer) {
|
||||||
|
fmt.Fprint(w, "name space {\n")
|
||||||
|
var all []string
|
||||||
|
for mtpt := range ns {
|
||||||
|
all = append(all, mtpt)
|
||||||
|
}
|
||||||
|
sort.Strings(all)
|
||||||
|
for _, mtpt := range all {
|
||||||
|
fmt.Fprintf(w, "\t%s:\n", mtpt)
|
||||||
|
for _, m := range ns[mtpt] {
|
||||||
|
fmt.Fprintf(w, "\t\t%s %s\n", m.fs, m.new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, "}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean returns a cleaned, rooted path for evaluation.
|
||||||
|
// It canonicalizes the path so that we can use string operations
|
||||||
|
// to analyze it.
|
||||||
|
func (NameSpace) clean(path string) string {
|
||||||
|
return pathpkg.Clean("/" + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BindMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BindReplace BindMode = iota
|
||||||
|
BindBefore
|
||||||
|
BindAfter
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bind causes references to old to redirect to the path new in newfs.
|
||||||
|
// If mode is BindReplace, old redirections are discarded.
|
||||||
|
// If mode is BindBefore, this redirection takes priority over existing ones,
|
||||||
|
// but earlier ones are still consulted for paths that do not exist in newfs.
|
||||||
|
// If mode is BindAfter, this redirection happens only after existing ones
|
||||||
|
// have been tried and failed.
|
||||||
|
func (ns NameSpace) Bind(old string, newfs FileSystem, new string, mode BindMode) {
|
||||||
|
old = ns.clean(old)
|
||||||
|
new = ns.clean(new)
|
||||||
|
m := mountedFS{old, newfs, new}
|
||||||
|
var mtpt []mountedFS
|
||||||
|
switch mode {
|
||||||
|
case BindReplace:
|
||||||
|
mtpt = append(mtpt, m)
|
||||||
|
case BindAfter:
|
||||||
|
mtpt = append(mtpt, ns.resolve(old)...)
|
||||||
|
mtpt = append(mtpt, m)
|
||||||
|
case BindBefore:
|
||||||
|
mtpt = append(mtpt, m)
|
||||||
|
mtpt = append(mtpt, ns.resolve(old)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend m.old, m.new in inherited mount point entries.
|
||||||
|
for i := range mtpt {
|
||||||
|
m := &mtpt[i]
|
||||||
|
if m.old != old {
|
||||||
|
if !hasPathPrefix(old, m.old) {
|
||||||
|
// This should not happen. If it does, panic so
|
||||||
|
// that we can see the call trace that led to it.
|
||||||
|
panic(fmt.Sprintf("invalid Bind: old=%q m={%q, %s, %q}", old, m.old, m.fs.String(), m.new))
|
||||||
|
}
|
||||||
|
suffix := old[len(m.old):]
|
||||||
|
m.old = pathpkg.Join(m.old, suffix)
|
||||||
|
m.new = pathpkg.Join(m.new, suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ns[old] = mtpt
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve resolves a path to the list of mountedFS to use for path.
|
||||||
|
func (ns NameSpace) resolve(path string) []mountedFS {
|
||||||
|
path = ns.clean(path)
|
||||||
|
for {
|
||||||
|
if m := ns[path]; m != nil {
|
||||||
|
if debugNS {
|
||||||
|
fmt.Printf("resolve %s: %v\n", path, m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
if path == "/" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
path = pathpkg.Dir(path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements the FileSystem Open method.
|
||||||
|
func (ns NameSpace) Open(path string) (ReadSeekCloser, error) {
|
||||||
|
var err error
|
||||||
|
for _, m := range ns.resolve(path) {
|
||||||
|
if debugNS {
|
||||||
|
fmt.Printf("tx %s: %v\n", path, m.translate(path))
|
||||||
|
}
|
||||||
|
r, err1 := m.fs.Open(m.translate(path))
|
||||||
|
if err1 == nil {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat implements the FileSystem Stat and Lstat methods.
|
||||||
|
func (ns NameSpace) stat(path string, f func(FileSystem, string) (os.FileInfo, error)) (os.FileInfo, error) {
|
||||||
|
var err error
|
||||||
|
for _, m := range ns.resolve(path) {
|
||||||
|
fi, err1 := f(m.fs, m.translate(path))
|
||||||
|
if err1 == nil {
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = &os.PathError{Op: "stat", Path: path, Err: os.ErrNotExist}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns NameSpace) Stat(path string) (os.FileInfo, error) {
|
||||||
|
return ns.stat(path, FileSystem.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns NameSpace) Lstat(path string) (os.FileInfo, error) {
|
||||||
|
return ns.stat(path, FileSystem.Lstat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirInfo is a trivial implementation of os.FileInfo for a directory.
|
||||||
|
type dirInfo string
|
||||||
|
|
||||||
|
func (d dirInfo) Name() string { return string(d) }
|
||||||
|
func (d dirInfo) Size() int64 { return 0 }
|
||||||
|
func (d dirInfo) Mode() os.FileMode { return os.ModeDir | 0555 }
|
||||||
|
func (d dirInfo) ModTime() time.Time { return startTime }
|
||||||
|
func (d dirInfo) IsDir() bool { return true }
|
||||||
|
func (d dirInfo) Sys() interface{} { return nil }
|
||||||
|
|
||||||
|
var startTime = time.Now()
|
||||||
|
|
||||||
|
// ReadDir implements the FileSystem ReadDir method. It's where most of the magic is.
|
||||||
|
// (The rest is in resolve.)
|
||||||
|
//
|
||||||
|
// Logically, ReadDir must return the union of all the directories that are named
|
||||||
|
// by path. In order to avoid misinterpreting Go packages, of all the directories
|
||||||
|
// that contain Go source code, we only include the files from the first,
|
||||||
|
// but we include subdirectories from all.
|
||||||
|
//
|
||||||
|
// ReadDir must also return directory entries needed to reach mount points.
|
||||||
|
// If the name space looks like the example in the type NameSpace comment,
|
||||||
|
// but c:\Go does not have a src/pkg subdirectory, we still want to be able
|
||||||
|
// to find that subdirectory, because we've mounted d:\Work1 and d:\Work2
|
||||||
|
// there. So if we don't see "src" in the directory listing for c:\Go, we add an
|
||||||
|
// entry for it before returning.
|
||||||
|
//
|
||||||
|
func (ns NameSpace) ReadDir(path string) ([]os.FileInfo, error) {
|
||||||
|
path = ns.clean(path)
|
||||||
|
|
||||||
|
var (
|
||||||
|
haveGo = false
|
||||||
|
haveName = map[string]bool{}
|
||||||
|
all []os.FileInfo
|
||||||
|
err error
|
||||||
|
first []os.FileInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, m := range ns.resolve(path) {
|
||||||
|
dir, err1 := m.fs.ReadDir(m.translate(path))
|
||||||
|
if err1 != nil {
|
||||||
|
if err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir == nil {
|
||||||
|
dir = []os.FileInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if first == nil {
|
||||||
|
first = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't yet have Go files in 'all' and this directory
|
||||||
|
// has some, add all the files from this directory.
|
||||||
|
// Otherwise, only add subdirectories.
|
||||||
|
useFiles := false
|
||||||
|
if !haveGo {
|
||||||
|
for _, d := range dir {
|
||||||
|
if strings.HasSuffix(d.Name(), ".go") {
|
||||||
|
useFiles = true
|
||||||
|
haveGo = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range dir {
|
||||||
|
name := d.Name()
|
||||||
|
if (d.IsDir() || useFiles) && !haveName[name] {
|
||||||
|
haveName[name] = true
|
||||||
|
all = append(all, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't find any directories containing Go files.
|
||||||
|
// If some directory returned successfully, use that.
|
||||||
|
if !haveGo {
|
||||||
|
for _, d := range first {
|
||||||
|
if !haveName[d.Name()] {
|
||||||
|
haveName[d.Name()] = true
|
||||||
|
all = append(all, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Built union. Add any missing directories needed to reach mount points.
|
||||||
|
for old := range ns {
|
||||||
|
if hasPathPrefix(old, path) && old != path {
|
||||||
|
// Find next element after path in old.
|
||||||
|
elem := old[len(path):]
|
||||||
|
elem = strings.TrimPrefix(elem, "/")
|
||||||
|
if i := strings.Index(elem, "/"); i >= 0 {
|
||||||
|
elem = elem[:i]
|
||||||
|
}
|
||||||
|
if !haveName[elem] {
|
||||||
|
haveName[elem] = true
|
||||||
|
all = append(all, dirInfo(elem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(all) == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byName(all))
|
||||||
|
return all, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// byName implements sort.Interface.
|
||||||
|
type byName []os.FileInfo
|
||||||
|
|
||||||
|
func (f byName) Len() int { return len(f) }
|
||||||
|
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
||||||
|
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
pathpkg "path"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OS returns an implementation of FileSystem reading from the
|
||||||
|
// tree rooted at root. Recording a root is convenient everywhere
|
||||||
|
// but necessary on Windows, because the slash-separated path
|
||||||
|
// passed to Open has no way to specify a drive letter. Using a root
|
||||||
|
// lets code refer to OS(`c:\`), OS(`d:\`) and so on.
|
||||||
|
func OS(root string) FileSystem {
|
||||||
|
return osFS(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
type osFS string
|
||||||
|
|
||||||
|
func (root osFS) String() string { return "os(" + string(root) + ")" }
|
||||||
|
|
||||||
|
func (root osFS) resolve(path string) string {
|
||||||
|
// Clean the path so that it cannot possibly begin with ../.
|
||||||
|
// If it did, the result of filepath.Join would be outside the
|
||||||
|
// tree rooted at root. We probably won't ever see a path
|
||||||
|
// with .. in it, but be safe anyway.
|
||||||
|
path = pathpkg.Clean("/" + path)
|
||||||
|
|
||||||
|
return filepath.Join(string(root), path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (root osFS) Open(path string) (ReadSeekCloser, error) {
|
||||||
|
f, err := os.Open(root.resolve(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
return nil, fmt.Errorf("Open: %s is a directory", path)
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (root osFS) Lstat(path string) (os.FileInfo, error) {
|
||||||
|
return os.Lstat(root.resolve(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (root osFS) Stat(path string) (os.FileInfo, error) {
|
||||||
|
return os.Stat(root.resolve(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (root osFS) ReadDir(path string) ([]os.FileInfo, error) {
|
||||||
|
return ioutil.ReadDir(root.resolve(path)) // is sorted
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,26 @@
|
||||||
// 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.
|
||||||
|
|
||||||
// Package vfs defines virtual filesystem types.
|
// Package vfs defines types for abstract file system access and provides an
|
||||||
|
// implementation accessing the file system of the underlying OS.
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Opener is a minimal virtual filesystem that can only open
|
// The FileSystem interface specifies the methods godoc is using
|
||||||
// regular files.
|
// to access the file system for which it serves documentation.
|
||||||
|
type FileSystem interface {
|
||||||
|
Opener
|
||||||
|
Lstat(path string) (os.FileInfo, error)
|
||||||
|
Stat(path string) (os.FileInfo, error)
|
||||||
|
ReadDir(path string) ([]os.FileInfo, error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opener is a minimal virtual filesystem that can only open regular files.
|
||||||
type Opener interface {
|
type Opener interface {
|
||||||
Open(name string) (ReadSeekCloser, error)
|
Open(name string) (ReadSeekCloser, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// 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 provides an implementation of the FileSystem
|
// Package zipfs file provides an implementation of the FileSystem
|
||||||
// interface based on the contents of a .zip file.
|
// interface based on the contents of a .zip file.
|
||||||
//
|
//
|
||||||
// Assumptions:
|
// Assumptions:
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
// like absolute paths w/o a leading '/'; i.e., the paths are considered
|
// like absolute paths w/o a leading '/'; i.e., the paths are considered
|
||||||
// relative to the root of the file system.
|
// relative to the root of the file system.
|
||||||
// - All path arguments to file system methods must be absolute paths.
|
// - All path arguments to file system methods must be absolute paths.
|
||||||
|
package zipvfs
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
|
@ -190,7 +189,7 @@ func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
|
||||||
return list, nil
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewZipFS(rc *zip.ReadCloser, name string) FileSystem {
|
func New(rc *zip.ReadCloser, name string) vfs.FileSystem {
|
||||||
list := make(zipList, len(rc.File))
|
list := make(zipList, len(rc.File))
|
||||||
copy(list, rc.File) // sort a copy of rc.File
|
copy(list, rc.File) // sort a copy of rc.File
|
||||||
sort.Sort(list)
|
sort.Sort(list)
|
||||||
Loading…
Reference in New Issue