Commit fae0d350 authored by Russ Cox's avatar Russ Cox

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
parent a347fdb0
......@@ -70,7 +70,7 @@
<p>
<span style="font-size:90%">
{{range .}}
<a href="/{{.|srcLink}}">{{.|filename|html}}</a>
<a href="{{.|srcLink|html}}">{{.|filename|html}}</a>
{{end}}
</span>
</p>
......
......@@ -42,8 +42,7 @@ func init() {
log.Fatalf("%s: %s\n", zipfile, err)
}
// rc is never closed (app running forever)
fs = NewZipFS(rc)
fsHttp = NewHttpZipFS(rc, *goroot)
fs.Bind("/", NewZipFS(rc, zipFilename), "/", bindReplace)
// initialize http handlers
readTemplates()
......@@ -53,9 +52,6 @@ func init() {
// initialize default directory tree with corresponding timestamp.
initFSTree()
// initialize directory trees for user-defined file systems (-path flag).
initDirTrees()
// Immediately update metadata.
updateMetadata()
......
......@@ -31,7 +31,7 @@ import (
// Handler for /doc/codewalk/ and below.
func codewalk(w http.ResponseWriter, r *http.Request) {
relpath := r.URL.Path[len("/doc/codewalk/"):]
abspath := absolutePath(r.URL.Path[1:], *goroot)
abspath := r.URL.Path
r.ParseForm()
if f := r.FormValue("fileprint"); f != "" {
......@@ -130,7 +130,7 @@ func loadCodewalk(filename string) (*Codewalk, error) {
i = len(st.Src)
}
filename := st.Src[0:i]
data, err := ReadFile(fs, absolutePath(filename, *goroot))
data, err := ReadFile(fs, filename)
if err != nil {
st.Err = err
continue
......@@ -208,7 +208,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
// of the codewalk pages. It is a separate iframe and does not get
// the usual godoc HTML wrapper.
func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
abspath := absolutePath(f, *goroot)
abspath := f
data, err := ReadFile(fs, abspath)
if err != nil {
log.Print(err)
......
......@@ -13,7 +13,7 @@ import (
"go/token"
"log"
"os"
"path/filepath"
pathpkg "path"
"strings"
)
......@@ -35,7 +35,7 @@ func isGoFile(fi os.FileInfo) bool {
name := fi.Name()
return !fi.IsDir() &&
len(name) > 0 && name[0] != '.' && // ignore .files
filepath.Ext(name) == ".go"
pathpkg.Ext(name) == ".go"
}
func isPkgFile(fi os.FileInfo) bool {
......@@ -50,12 +50,11 @@ func isPkgDir(fi os.FileInfo) bool {
}
type treeBuilder struct {
pathFilter func(string) bool
maxDepth int
maxDepth int
}
func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory {
if b.pathFilter != nil && !b.pathFilter(path) || name == testdataDirName {
if name == testdataDirName {
return nil
}
......@@ -92,7 +91,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)
if synopses[0] == "" {
// no "optimal" package synopsis yet; continue to collect synopses
file, err := parseFile(fset, filepath.Join(path, d.Name()),
file, err := parseFile(fset, pathpkg.Join(path, d.Name()),
parser.ParseComments|parser.PackageClauseOnly)
if err == nil {
hasPkgFiles = true
......@@ -126,7 +125,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
for _, d := range list {
if isPkgDir(d) {
name := d.Name()
dd := b.newDirTree(fset, filepath.Join(path, name), name, depth+1)
dd := b.newDirTree(fset, pathpkg.Join(path, name), name, depth+1)
if dd != nil {
dirs[i] = dd
i++
......@@ -170,7 +169,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
// are assumed to contain package files even if their contents are not known
// (i.e., in this case the tree may contain directories w/o any package files).
//
func newDirectory(root string, pathFilter func(string) bool, maxDepth int) *Directory {
func newDirectory(root string, maxDepth int) *Directory {
// The root could be a symbolic link so use Stat not Lstat.
d, err := fs.Stat(root)
// If we fail here, report detailed error messages; otherwise
......@@ -186,7 +185,7 @@ func newDirectory(root string, pathFilter func(string) bool, maxDepth int) *Dire
if maxDepth < 0 {
maxDepth = 1e6 // "infinity"
}
b := treeBuilder{pathFilter, maxDepth}
b := treeBuilder{maxDepth}
// the file set provided is only for local parsing, no position
// information escapes and thus we don't need to save the set
return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
......@@ -235,10 +234,20 @@ func (dir *Directory) lookupLocal(name string) *Directory {
return nil
}
func splitPath(p string) []string {
if strings.HasPrefix(p, "/") {
p = p[1:]
}
if p == "" {
return nil
}
return strings.Split(p, "/")
}
// lookup looks for the *Directory for a given path, relative to dir.
func (dir *Directory) lookup(path string) *Directory {
d := strings.Split(dir.Path, string(filepath.Separator))
p := strings.Split(path, string(filepath.Separator))
d := splitPath(dir.Path)
p := splitPath(path)
i := 0
for i < len(d) {
if i >= len(p) || d[i] != p[i] {
......@@ -311,8 +320,8 @@ func (root *Directory) listing(skipRoot bool) *DirList {
if strings.HasPrefix(d.Path, root.Path) {
path = d.Path[len(root.Path):]
}
// remove trailing separator if any - path must be relative
if len(path) > 0 && path[0] == filepath.Separator {
// remove leading separator if any - path must be relative
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
p.Path = path
......
This diff is collapsed.
This diff is collapsed.
// 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 provides an implementation of the http.FileSystem
// interface based on the contents of a .zip file.
//
// Assumptions:
//
// - The file paths stored in the zip file must use a slash ('/') as path
// separator; and they must be relative (i.e., they must not start with
// a '/' - this is usually the case if the file was created w/o special
// options).
// - The zip file system treats the file paths found in the zip internally
// like absolute paths w/o a leading '/'; i.e., the paths are considered
// relative to the root of the file system.
// - All path arguments to file system methods are considered relative to
// the root specified with NewHttpZipFS (even if the paths start with a '/').
// TODO(gri) Should define a commonly used FileSystem API that is the same
// for http and godoc. Then we only need one zip-file based file
// system implementation.
package main
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path"
"sort"
"strings"
"time"
)
type fileInfo struct {
name string
mode os.FileMode
size int64
mtime time.Time
}
func (fi *fileInfo) Name() string { return fi.name }
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
func (fi *fileInfo) Size() int64 { return fi.size }
func (fi *fileInfo) ModTime() time.Time { return fi.mtime }
func (fi *fileInfo) IsDir() bool { return fi.mode.IsDir() }
func (fi *fileInfo) Sys() interface{} { return nil }
// httpZipFile is the zip-file based implementation of http.File
type httpZipFile struct {
path string // absolute path within zip FS without leading '/'
info os.FileInfo
io.ReadCloser // nil for directory
list zipList
}
func (f *httpZipFile) Close() error {
if !f.info.IsDir() {
return f.ReadCloser.Close()
}
f.list = nil
return nil
}
func (f *httpZipFile) Stat() (os.FileInfo, error) {
return f.info, nil
}
func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, error) {
var list []os.FileInfo
dirname := f.path + "/"
prevname := ""
for i, e := range f.list {
if count == 0 {
f.list = f.list[i:]
break
}
if !strings.HasPrefix(e.Name, dirname) {
f.list = nil
break // not in the same directory anymore
}
name := e.Name[len(dirname):] // local name
var mode os.FileMode
var size int64
var mtime time.Time
if i := strings.IndexRune(name, '/'); i >= 0 {
// We infer directories from files in subdirectories.
// If we have x/y, return a directory entry for x.
name = name[0:i] // keep local directory name only
mode = os.ModeDir
// no size or mtime for directories
} else {
mode = 0
size = int64(e.UncompressedSize)
mtime = e.ModTime()
}
// If we have x/y and x/z, don't return two directory entries for x.
// TODO(gri): It should be possible to do this more efficiently
// by determining the (fs.list) range of local directory entries
// (via two binary searches).
if name != prevname {
list = append(list, &fileInfo{
name,
mode,
size,
mtime,
})
prevname = name
count--
}
}
if count >= 0 && len(list) == 0 {
return nil, io.EOF
}
return list, nil
}
func (f *httpZipFile) Seek(offset int64, whence int) (int64, error) {
return 0, fmt.Errorf("Seek not implemented for zip file entry: %s", f.info.Name())
}
// httpZipFS is the zip-file based implementation of http.FileSystem
type httpZipFS struct {
*zip.ReadCloser
list zipList
root string
}
func (fs *httpZipFS) Open(name string) (http.File, error) {
// fs.root does not start with '/'.
path := path.Join(fs.root, name) // path is clean
index, exact := fs.list.lookup(path)
if index < 0 || !strings.HasPrefix(path, fs.root) {
// file not found or not under root
return nil, fmt.Errorf("file not found: %s", name)
}
if exact {
// exact match found - must be a file
f := fs.list[index]
rc, err := f.Open()
if err != nil {
return nil, err
}
return &httpZipFile{
path,
&fileInfo{
name,
0,
int64(f.UncompressedSize),
f.ModTime(),
},
rc,
nil,
}, nil
}
// not an exact match - must be a directory
return &httpZipFile{
path,
&fileInfo{
name,
os.ModeDir,
0, // no size for directory
time.Time{}, // no mtime for directory
},
nil,
fs.list[index:],
}, nil
}
func (fs *httpZipFS) Close() error {
fs.list = nil
return fs.ReadCloser.Close()
}
// NewHttpZipFS creates a new http.FileSystem based on the contents of
// the zip file rc restricted to the directory tree specified by root;
// root must be an absolute path.
func NewHttpZipFS(rc *zip.ReadCloser, root string) http.FileSystem {
list := make(zipList, len(rc.File))
copy(list, rc.File) // sort a copy of rc.File
sort.Sort(list)
return &httpZipFS{rc, list, zipPath(root)}
}
......@@ -48,7 +48,7 @@ import (
"index/suffixarray"
"io"
"os"
"path/filepath"
pathpkg "path"
"regexp"
"sort"
"strings"
......@@ -248,7 +248,7 @@ type File struct {
// Path returns the file path of f.
func (f *File) Path() string {
return filepath.Join(f.Pak.Path, f.Name)
return pathpkg.Join(f.Pak.Path, f.Name)
}
// A Spot describes a single occurrence of a word.
......@@ -695,7 +695,7 @@ var whitelisted = map[string]bool{
// of "permitted" files for indexing. The filename must
// be the directory-local name of the file.
func isWhitelisted(filename string) bool {
key := filepath.Ext(filename)
key := pathpkg.Ext(filename)
if key == "" {
// file has no extension - use entire filename
key = filename
......@@ -708,7 +708,7 @@ func (x *Indexer) visitFile(dirname string, f os.FileInfo, fulltextIndex bool) {
return
}
filename := filepath.Join(dirname, f.Name())
filename := pathpkg.Join(dirname, f.Name())
goFile := false
switch {
......
......@@ -39,7 +39,7 @@ import (
"net/http"
_ "net/http/pprof" // to serve /debug/pprof/*
"os"
"path"
pathpkg "path"
"path/filepath"
"regexp"
"runtime"
......@@ -239,19 +239,20 @@ func main() {
// same is true for the http handlers in initHandlers.
if *zipfile == "" {
// use file system of underlying OS
*goroot = filepath.Clean(*goroot) // normalize path separator
fs = OS
fsHttp = http.Dir(*goroot)
fs.Bind("/", OS(*goroot), "/", bindReplace)
} else {
// use file system specified via .zip file (path separator must be '/')
rc, err := zip.OpenReader(*zipfile)
if err != nil {
log.Fatalf("%s: %s\n", *zipfile, err)
}
defer rc.Close() // be nice (e.g., -writeIndex mode)
*goroot = path.Join("/", *goroot) // fsHttp paths are relative to '/'
fs = NewZipFS(rc)
fsHttp = NewHttpZipFS(rc, *goroot)
defer rc.Close() // be nice (e.g., -writeIndex mode)
fs.Bind("/", NewZipFS(rc, *zipfile), *goroot, bindReplace)
}
// Bind $GOPATH trees into Go root.
for _, p := range filepath.SplitList(build.Default.GOPATH) {
fs.Bind("/src/pkg", OS(p), "/src", bindAfter)
}
readTemplates()
......@@ -266,7 +267,6 @@ func main() {
log.Println("initialize file systems")
*verbose = true // want to see what happens
initFSTree()
initDirTrees()
*indexThrottle = 1
updateIndex()
......@@ -303,10 +303,7 @@ func main() {
default:
log.Print("identifier search index enabled")
}
if !fsMap.IsEmpty() {
log.Print("user-defined mapping:")
fsMap.Fprint(os.Stderr)
}
fs.Fprint(os.Stderr)
handler = loggingHandler(handler)
}
......@@ -319,9 +316,6 @@ func main() {
// (Do it in a goroutine so that launch is quick.)
go initFSTree()
// Initialize directory trees for user-defined file systems (-path flag).
initDirTrees()
// Start sync goroutine, if enabled.
if *syncCmd != "" && *syncMin > 0 {
syncDelay.set(*syncMin) // initial sync delay
......@@ -378,23 +372,27 @@ func main() {
const cmdPrefix = "cmd/"
path := flag.Arg(0)
var forceCmd bool
if strings.HasPrefix(path, ".") {
// assume cwd; don't assume -goroot
var abspath, relpath string
if filepath.IsAbs(path) {
fs.Bind("/target", OS(path), "/", bindReplace)
abspath = "/target"
} else if build.IsLocalImport(path) {
cwd, _ := os.Getwd() // ignore errors
path = filepath.Join(cwd, path)
fs.Bind("/target", OS(path), "/", bindReplace)
abspath = "/target"
} else if strings.HasPrefix(path, cmdPrefix) {
path = path[len(cmdPrefix):]
abspath = path[len(cmdPrefix):]
forceCmd = true
}
relpath := path
abspath := path
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)
abspath = "/target"
relpath = bp.ImportPath
abspath = bp.Dir
} else if !filepath.IsAbs(path) {
abspath = absolutePath(path, pkgHandler.fsRoot)
} else {
relpath = relativeURL(path)
abspath = pathpkg.Join(pkgHandler.fsRoot, path)
}
if relpath == "" {
relpath = abspath
}
var mode PageInfoMode
......@@ -422,7 +420,7 @@ func main() {
// (the go command invokes godoc w/ absolute paths; don't override)
var cinfo PageInfo
if !filepath.IsAbs(path) {
abspath = absolutePath(path, cmdHandler.fsRoot)
abspath = pathpkg.Join(cmdHandler.fsRoot, path)
cinfo = cmdHandler.getPageInfo(abspath, relpath, "", mode)
}
......@@ -445,6 +443,9 @@ func main() {
if info.Err != nil {
log.Fatalf("%v", info.Err)
}
if info.PDoc.ImportPath == "/target" {
info.PDoc.ImportPath = flag.Arg(0)
}
// If we have more than one argument, use the remaining arguments for filtering
if flag.NArg() > 1 {
......
// Copyright 2009 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 implements the Mapping data structure.
package main
import (
"fmt"
"io"
"path"
"path/filepath"
"sort"
"strings"
)
// A Mapping object maps relative paths (e.g. from URLs)
// to absolute paths (of the file system) and vice versa.
//
// A Mapping object consists of a list of individual mappings
// of the form: prefix -> path which are interpreted as follows:
// A relative path of the form prefix/tail is to be mapped to
// the absolute path/tail, if that absolute path exists in the file
// system. Given a Mapping object, a relative path is mapped to an
// absolute path by trying each of the individual mappings in order,
// until a valid mapping is found. For instance, for the mapping:
//
// user -> /home/user
// public -> /home/user/public
// public -> /home/build/public
//
// the relative paths below are mapped to absolute paths as follows:
//
// user/foo -> /home/user/foo
// public/net/rpc/file1.go -> /home/user/public/net/rpc/file1.go
//
// If there is no /home/user/public/net/rpc/file2.go, the next public
// mapping entry is used to map the relative path to:
//
// public/net/rpc/file2.go -> /home/build/public/net/rpc/file2.go
//
// (assuming that file exists).
//
// Each individual mapping also has a RWValue associated with it that
// may be used to store mapping-specific information. See the Iterate
// method.
//
type Mapping struct {
list []mapping
prefixes []string // lazily computed from list
}
type mapping struct {
prefix, path string
value *RWValue
}
// Init initializes the Mapping from a list of paths.
// Empty paths are ignored; relative paths are assumed to be relative to
// the current working directory and converted to absolute paths.
// For each path of the form:
//
// dirname/localname
//
// a mapping
//
// localname -> path
//
// is added to the Mapping object, in the order of occurrence.
// For instance, under Unix, the argument:
//
// /home/user:/home/build/public
//
// leads to the following mapping:
//
// user -> /home/user
// public -> /home/build/public
//
func (m *Mapping) Init(paths []string) {
pathlist := canonicalizePaths(paths, nil)
list := make([]mapping, len(pathlist))
// create mapping list
for i, path := range pathlist {
_, prefix := filepath.Split(path)
list[i] = mapping{prefix, path, new(RWValue)}
}
m.list = list
}
// IsEmpty returns true if there are no mappings specified.
func (m *Mapping) IsEmpty() bool { return len(m.list) == 0 }
// PrefixList returns a list of all prefixes, with duplicates removed.
// For instance, for the mapping:
//
// user -> /home/user
// public -> /home/user/public
// public -> /home/build/public
//
// the prefix list is:
//
// user, public
//
func (m *Mapping) PrefixList() []string {
// compute the list lazily
if m.prefixes == nil {
list := make([]string, len(m.list))
// populate list
for i, e := range m.list {
list[i] = e.prefix
}
// sort the list and remove duplicate entries
sort.Strings(list)
i := 0
prev := ""
for _, path := range list {
if path != prev {
list[i] = path
i++
prev = path
}
}
m.prefixes = list[0:i]
}
return m.prefixes
}
// Fprint prints the mapping.
func (m *Mapping) Fprint(w io.Writer) {
for _, e := range m.list {
fmt.Fprintf(w, "\t%s -> %s\n", e.prefix, e.path)
}
}
const sep = string(filepath.Separator)
// splitFirst splits a path at the first path separator and returns
// the path's head (the top-most directory specified by the path) and
// its tail (the rest of the path). If there is no path separator,
// splitFirst returns path as head, and the empty string as tail.
// Specifically, splitFirst("foo") == splitFirst("foo/").
//
func splitFirst(path string) (head, tail string) {
if i := strings.Index(path, sep); i > 0 {
// 0 < i < len(path)
return path[0:i], path[i+1:]
}
return path, ""
}
// ToAbsolute maps a slash-separated relative path to an absolute filesystem
// path using the Mapping specified by the receiver. If the path cannot
// be mapped, the empty string is returned.
//
func (m *Mapping) ToAbsolute(spath string) string {
fpath := filepath.FromSlash(spath)
prefix, tail := splitFirst(fpath)
for _, e := range m.list {
if e.prefix == prefix {
// found potential mapping
abspath := filepath.Join(e.path, tail)
if _, err := fs.Stat(abspath); err == nil {
return abspath
}
}
}
return "" // no match
}
// ToRelative maps an absolute filesystem path to a relative slash-separated
// path using the Mapping specified by the receiver. If the path cannot
// be mapped, the empty string is returned.
//
func (m *Mapping) ToRelative(fpath string) string {
for _, e := range m.list {
// if fpath has prefix e.path, the next character must be a separator (was issue 3096)
if strings.HasPrefix(fpath, e.path+sep) {
spath := filepath.ToSlash(fpath)
// /absolute/prefix/foo -> prefix/foo
return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
}
}
return "" // no match
}
// Iterate calls f for each path and RWValue in the mapping (in uspecified order)
// until f returns false.
//
func (m *Mapping) Iterate(f func(path string, value *RWValue) bool) {
for _, e := range m.list {
if !f(e.path, e.value) {
return
}
}
}
......@@ -14,7 +14,7 @@ import (
"go/parser"
"go/token"
"os"
"path/filepath"
pathpkg "path"
)
func parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
......@@ -58,7 +58,7 @@ func parseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool) (
i := 0
for _, d := range list {
if filter == nil || filter(d) {
filenames[i] = filepath.Join(path, d.Name())
filenames[i] = pathpkg.Join(path, d.Name())
i++
}
}
......
......@@ -7,12 +7,7 @@
package main
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
pathpkg "path"
"sync"
"time"
"unicode/utf8"
......@@ -40,76 +35,6 @@ func (v *RWValue) get() (interface{}, time.Time) {
return v.value, v.timestamp
}
// 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
// canonicalizePaths takes a list of (directory/file) paths and returns
// the list of corresponding absolute paths in sorted (increasing) order.
// Relative paths are assumed to be relative to the current directory,
// empty and duplicate paths as well as paths for which filter(path) is
// false are discarded. filter may be nil in which case it is not used.
//
func canonicalizePaths(list []string, filter func(path string) bool) []string {
i := 0
for _, path := range list {
path = strings.TrimSpace(path)
if len(path) == 0 {
continue // ignore empty paths (don't assume ".")
}
// len(path) > 0: normalize path
if filepath.IsAbs(path) {
path = filepath.Clean(path)
} else {
path = filepath.Join(cwd, path)
}
// we have a non-empty absolute path
if filter != nil && !filter(path) {
continue
}
// keep the path
list[i] = path
i++
}
list = list[0:i]
// sort the list and remove duplicate entries
sort.Strings(list)
i = 0
prev := ""
for _, path := range list {
if path != prev {
list[i] = path
i++
prev = path
}
}
return list[0:i]
}
// writeFileAtomically writes data to a temporary file and then
// atomically renames that file to the file named by filename.
//
func writeFileAtomically(filename string, data []byte) error {
// TODO(gri) this won't work on appengine
f, err := ioutil.TempFile(filepath.Split(filename))
if err != nil {
return err
}
n, err := f.Write(data)
f.Close()
if err != nil {
return err
}
if n < len(data) {
return io.ErrShortWrite
}
return os.Rename(f.Name(), filename)
}
// isText returns true if a significant prefix of s looks like correct UTF-8;
// that is, if it is likely that s is human-readable text.
//
......@@ -146,7 +71,7 @@ var textExt = map[string]bool{
//
func isTextFile(filename string) bool {
// if the extension is known, use it for decision making
if isText, found := textExt[filepath.Ext(filename)]; found {
if isText, found := textExt[pathpkg.Ext(filename)]; found {
return isText
}
......
......@@ -73,6 +73,11 @@ func (fi zipFI) Sys() interface{} {
type zipFS struct {
*zip.ReadCloser
list zipList
name string
}
func (fs *zipFS) String() string {
return "zip(" + fs.name + ")"
}
func (fs *zipFS) Close() error {
......@@ -102,7 +107,7 @@ func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
return i, zipFI{name, file}, nil
}
func (fs *zipFS) Open(abspath string) (io.ReadCloser, error) {
func (fs *zipFS) Open(abspath string) (readSeekCloser, error) {
_, fi, err := fs.stat(zipPath(abspath))
if err != nil {
return nil, err
......@@ -110,7 +115,29 @@ func (fs *zipFS) Open(abspath string) (io.ReadCloser, error) {
if fi.IsDir() {
return nil, fmt.Errorf("Open: %s is a directory", abspath)
}
return fi.file.Open()
r, err := fi.file.Open()
if err != nil {
return nil, err
}
return &zipSeek{fi.file, r}, nil
}
type zipSeek struct {
file *zip.File
io.ReadCloser
}
func (f *zipSeek) Seek(offset int64, whence int) (int64, error) {
if whence == 0 && offset == 0 {
r, err := f.file.Open()
if err != nil {
return 0, err
}
f.Close()
f.ReadCloser = r
return 0, nil
}
return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name)
}
func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) {
......@@ -161,11 +188,11 @@ func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
return list, nil
}
func NewZipFS(rc *zip.ReadCloser) FileSystem {
func NewZipFS(rc *zip.ReadCloser, name string) FileSystem {
list := make(zipList, len(rc.File))
copy(list, rc.File) // sort a copy of rc.File
sort.Sort(list)
return &zipFS{rc, list}
return &zipFS{rc, list, name}
}
type zipList []*zip.File
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment