// 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 mapfs file provides an implementation of the FileSystem // interface based on the contents of a map[string]string. package mapfs // import "golang.org/x/tools/godoc/vfs/mapfs" import ( "fmt" "io" "os" pathpkg "path" "sort" "strings" "time" "golang.org/x/tools/godoc/vfs" ) // New returns a new FileSystem from the provided map. // Map keys must be forward slash-separated paths with // no leading slash, such as "file1.txt" or "dir/file2.txt". // New panics if any of the paths contain a leading slash. func New(m map[string]string) vfs.FileSystem { // Verify all provided paths are relative before proceeding. var pathsWithLeadingSlash []string for p := range m { if strings.HasPrefix(p, "/") { pathsWithLeadingSlash = append(pathsWithLeadingSlash, p) } } if len(pathsWithLeadingSlash) > 0 { panic(fmt.Errorf("mapfs.New: invalid paths with a leading slash: %q", pathsWithLeadingSlash)) } return mapFS(m) } // mapFS is the map based implementation of FileSystem type mapFS map[string]string func (fs mapFS) String() string { return "mapfs" } func (fs mapFS) RootType(p string) vfs.RootType { return "" } func (fs mapFS) Close() error { return nil } func filename(p string) string { return strings.TrimPrefix(p, "/") } func (fs mapFS) Open(p string) (vfs.ReadSeekCloser, error) { b, ok := fs[filename(p)] if !ok { return nil, os.ErrNotExist } return nopCloser{strings.NewReader(b)}, nil } func fileInfo(name, contents string) os.FileInfo { return mapFI{name: pathpkg.Base(name), size: len(contents)} } func dirInfo(name string) os.FileInfo { return mapFI{name: pathpkg.Base(name), dir: true} } func (fs mapFS) Lstat(p string) (os.FileInfo, error) { b, ok := fs[filename(p)] if ok { return fileInfo(p, b), nil } ents, _ := fs.ReadDir(p) if len(ents) > 0 { return dirInfo(p), nil } return nil, os.ErrNotExist } func (fs mapFS) Stat(p string) (os.FileInfo, error) { return fs.Lstat(p) } // slashdir returns path.Dir(p), but special-cases paths not beginning // with a slash to be in the root. func slashdir(p string) string { d := pathpkg.Dir(p) if d == "." { return "/" } if strings.HasPrefix(p, "/") { return d } return "/" + d } func (fs mapFS) ReadDir(p string) ([]os.FileInfo, error) { p = pathpkg.Clean(p) var ents []string fim := make(map[string]os.FileInfo) // base -> fi for fn, b := range fs { dir := slashdir(fn) isFile := true var lastBase string for { if dir == p { base := lastBase if isFile { base = pathpkg.Base(fn) } if fim[base] == nil { var fi os.FileInfo if isFile { fi = fileInfo(fn, b) } else { fi = dirInfo(base) } ents = append(ents, base) fim[base] = fi } } if dir == "/" { break } else { isFile = false lastBase = pathpkg.Base(dir) dir = pathpkg.Dir(dir) } } } if len(ents) == 0 { return nil, os.ErrNotExist } sort.Strings(ents) var list []os.FileInfo for _, dir := range ents { list = append(list, fim[dir]) } return list, nil } // mapFI is the map-based implementation of FileInfo. type mapFI struct { name string size int dir bool } func (fi mapFI) IsDir() bool { return fi.dir } func (fi mapFI) ModTime() time.Time { return time.Time{} } func (fi mapFI) Mode() os.FileMode { if fi.IsDir() { return 0755 | os.ModeDir } return 0444 } func (fi mapFI) Name() string { return pathpkg.Base(fi.name) } func (fi mapFI) Size() int64 { return int64(fi.size) } func (fi mapFI) Sys() interface{} { return nil } type nopCloser struct { io.ReadSeeker } func (nc nopCloser) Close() error { return nil }