2011-06-15 15:06:35 -06:00
|
|
|
// 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.
|
|
|
|
|
2011-07-14 12:34:53 -06:00
|
|
|
// This file defines types for abstract file system access and
|
|
|
|
// provides an implementation accessing the file system of the
|
|
|
|
// underlying OS.
|
2011-06-15 15:06:35 -06:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2011-07-14 12:34:53 -06:00
|
|
|
"fmt"
|
2011-06-15 15:06:35 -06:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
"net/http"
|
2011-06-15 15:06:35 -06:00
|
|
|
"os"
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
pathpkg "path"
|
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2011-06-15 15:06:35 -06:00
|
|
|
)
|
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
// 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 below.
|
|
|
|
//
|
|
|
|
// 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 = nameSpace{} // the underlying file system for godoc
|
|
|
|
|
|
|
|
// Setting debugNS = true will enable debugging prints about
|
|
|
|
// name space translations.
|
|
|
|
const debugNS = false
|
|
|
|
|
2011-06-15 15:06:35 -06:00
|
|
|
// The FileSystem interface specifies the methods godoc is using
|
|
|
|
// to access the file system for which it serves documentation.
|
|
|
|
type FileSystem interface {
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
Open(path string) (readSeekCloser, error)
|
2011-11-30 10:04:16 -07:00
|
|
|
Lstat(path string) (os.FileInfo, error)
|
|
|
|
Stat(path string) (os.FileInfo, error)
|
|
|
|
ReadDir(path string) ([]os.FileInfo, error)
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
String() string
|
|
|
|
}
|
|
|
|
|
|
|
|
type readSeekCloser interface {
|
|
|
|
io.Reader
|
|
|
|
io.Seeker
|
|
|
|
io.Closer
|
2011-09-21 16:12:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadFile reads the file named by path from fs and returns the contents.
|
2011-11-01 20:06:05 -06:00
|
|
|
func ReadFile(fs FileSystem, path string) ([]byte, error) {
|
2011-09-21 16:12:06 -06:00
|
|
|
rc, err := fs.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rc.Close()
|
|
|
|
return ioutil.ReadAll(rc)
|
2011-06-15 15:06:35 -06:00
|
|
|
}
|
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
// 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
|
2011-06-15 15:06:35 -06:00
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
func (root osFS) String() string { return "os(" + string(root) + ")" }
|
2011-06-15 15:06:35 -06:00
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
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)
|
2011-06-15 15:06:35 -06:00
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
return filepath.Join(string(root), path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (root osFS) Open(path string) (readSeekCloser, error) {
|
|
|
|
f, err := os.Open(root.resolve(path))
|
2011-07-14 12:34:53 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fi, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2011-11-30 10:04:16 -07:00
|
|
|
if fi.IsDir() {
|
2011-07-14 12:34:53 -06:00
|
|
|
return nil, fmt.Errorf("Open: %s is a directory", path)
|
|
|
|
}
|
|
|
|
return f, nil
|
2011-06-15 15:06:35 -06:00
|
|
|
}
|
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
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) (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{"open", path, 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{"stat", path, 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
|
2012-03-05 11:29:13 -07:00
|
|
|
first []os.FileInfo
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
for _, m := range ns.resolve(path) {
|
|
|
|
dir, err1 := m.fs.ReadDir(m.translate(path))
|
|
|
|
if err1 != nil {
|
|
|
|
if err == nil {
|
|
|
|
err = err1
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2012-03-05 11:29:13 -07:00
|
|
|
if dir == nil {
|
|
|
|
dir = []os.FileInfo{}
|
|
|
|
}
|
|
|
|
|
|
|
|
if first == nil {
|
|
|
|
first = dir
|
|
|
|
}
|
|
|
|
|
2012-03-11 22:55:39 -06:00
|
|
|
useFiles := false
|
|
|
|
|
|
|
|
// Always include all files under /doc.
|
|
|
|
if path == "/doc" || strings.HasPrefix(path, "/doc/") {
|
|
|
|
useFiles = true // always include docs
|
|
|
|
}
|
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
// 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.
|
2012-03-11 22:55:39 -06:00
|
|
|
if !useFiles && !haveGo {
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-05 11:29:13 -07:00
|
|
|
// We didn't find any directories containing Go files.
|
|
|
|
// If some directory returned successfully, use that.
|
|
|
|
if len(all) == 0 && first != nil {
|
|
|
|
for _, d := range first {
|
|
|
|
haveName[d.Name()] = true
|
|
|
|
all = append(all, d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
// 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):]
|
|
|
|
if strings.HasPrefix(elem, "/") {
|
|
|
|
elem = elem[1:]
|
|
|
|
}
|
|
|
|
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
|
2011-06-15 15:06:35 -06:00
|
|
|
}
|
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
// httpFile implements http.File for a file (not directory) in a FileSystem.
|
|
|
|
type httpFile struct {
|
|
|
|
fs FileSystem
|
|
|
|
readSeekCloser
|
|
|
|
name string
|
2011-06-15 15:06:35 -06:00
|
|
|
}
|
|
|
|
|
godoc: support $GOPATH, simplify file system code
The motivation for this CL is to support $GOPATH well.
Since we already have a FileSystem interface, implement a
Plan 9-style name space. Bind each of the $GOPATH src
directories onto the $GOROOT src/pkg directory: now
everything is laid out exactly like a normal $GOROOT and
needs very little special case code.
The filter files are no longer used (by us), so I think they
can just be deleted. Similarly, the Mapping code and the
FileSystem interface were two different ways to accomplish
the same end, so delete the Mapping code.
Within the implementation, since FileSystem is defined to be
slash-separated, use package path consistently, leaving
path/filepath only for manipulating operating system paths.
I kept the -path flag, but I think it can be deleted too.
Fixes #2234.
Fixes #3046.
R=gri, r, r, rsc
CC=golang-dev
https://golang.org/cl/5711058
2012-03-05 08:02:46 -07:00
|
|
|
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)
|
2011-06-15 15:06:35 -06:00
|
|
|
}
|