| 1 | // Copyright 2013 The Go Authors. All rights reserved. |
|---|---|
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | // Package mapfs file provides an implementation of the FileSystem |
| 6 | // interface based on the contents of a map[string]string. |
| 7 | package mapfs // import "golang.org/x/tools/godoc/vfs/mapfs" |
| 8 | |
| 9 | import ( |
| 10 | "fmt" |
| 11 | "io" |
| 12 | "os" |
| 13 | pathpkg "path" |
| 14 | "sort" |
| 15 | "strings" |
| 16 | "time" |
| 17 | |
| 18 | "golang.org/x/tools/godoc/vfs" |
| 19 | ) |
| 20 | |
| 21 | // New returns a new FileSystem from the provided map. |
| 22 | // Map keys must be forward slash-separated paths with |
| 23 | // no leading slash, such as "file1.txt" or "dir/file2.txt". |
| 24 | // New panics if any of the paths contain a leading slash. |
| 25 | func New(m map[string]string) vfs.FileSystem { |
| 26 | // Verify all provided paths are relative before proceeding. |
| 27 | var pathsWithLeadingSlash []string |
| 28 | for p := range m { |
| 29 | if strings.HasPrefix(p, "/") { |
| 30 | pathsWithLeadingSlash = append(pathsWithLeadingSlash, p) |
| 31 | } |
| 32 | } |
| 33 | if len(pathsWithLeadingSlash) > 0 { |
| 34 | panic(fmt.Errorf("mapfs.New: invalid paths with a leading slash: %q", pathsWithLeadingSlash)) |
| 35 | } |
| 36 | |
| 37 | return mapFS(m) |
| 38 | } |
| 39 | |
| 40 | // mapFS is the map based implementation of FileSystem |
| 41 | type mapFS map[string]string |
| 42 | |
| 43 | func (fs mapFS) String() string { return "mapfs" } |
| 44 | |
| 45 | func (fs mapFS) RootType(p string) vfs.RootType { |
| 46 | return "" |
| 47 | } |
| 48 | |
| 49 | func (fs mapFS) Close() error { return nil } |
| 50 | |
| 51 | func filename(p string) string { |
| 52 | return strings.TrimPrefix(p, "/") |
| 53 | } |
| 54 | |
| 55 | func (fs mapFS) Open(p string) (vfs.ReadSeekCloser, error) { |
| 56 | b, ok := fs[filename(p)] |
| 57 | if !ok { |
| 58 | return nil, os.ErrNotExist |
| 59 | } |
| 60 | return nopCloser{strings.NewReader(b)}, nil |
| 61 | } |
| 62 | |
| 63 | func fileInfo(name, contents string) os.FileInfo { |
| 64 | return mapFI{name: pathpkg.Base(name), size: len(contents)} |
| 65 | } |
| 66 | |
| 67 | func dirInfo(name string) os.FileInfo { |
| 68 | return mapFI{name: pathpkg.Base(name), dir: true} |
| 69 | } |
| 70 | |
| 71 | func (fs mapFS) Lstat(p string) (os.FileInfo, error) { |
| 72 | b, ok := fs[filename(p)] |
| 73 | if ok { |
| 74 | return fileInfo(p, b), nil |
| 75 | } |
| 76 | ents, _ := fs.ReadDir(p) |
| 77 | if len(ents) > 0 { |
| 78 | return dirInfo(p), nil |
| 79 | } |
| 80 | return nil, os.ErrNotExist |
| 81 | } |
| 82 | |
| 83 | func (fs mapFS) Stat(p string) (os.FileInfo, error) { |
| 84 | return fs.Lstat(p) |
| 85 | } |
| 86 | |
| 87 | // slashdir returns path.Dir(p), but special-cases paths not beginning |
| 88 | // with a slash to be in the root. |
| 89 | func slashdir(p string) string { |
| 90 | d := pathpkg.Dir(p) |
| 91 | if d == "." { |
| 92 | return "/" |
| 93 | } |
| 94 | if strings.HasPrefix(p, "/") { |
| 95 | return d |
| 96 | } |
| 97 | return "/" + d |
| 98 | } |
| 99 | |
| 100 | func (fs mapFS) ReadDir(p string) ([]os.FileInfo, error) { |
| 101 | p = pathpkg.Clean(p) |
| 102 | var ents []string |
| 103 | fim := make(map[string]os.FileInfo) // base -> fi |
| 104 | for fn, b := range fs { |
| 105 | dir := slashdir(fn) |
| 106 | isFile := true |
| 107 | var lastBase string |
| 108 | for { |
| 109 | if dir == p { |
| 110 | base := lastBase |
| 111 | if isFile { |
| 112 | base = pathpkg.Base(fn) |
| 113 | } |
| 114 | if fim[base] == nil { |
| 115 | var fi os.FileInfo |
| 116 | if isFile { |
| 117 | fi = fileInfo(fn, b) |
| 118 | } else { |
| 119 | fi = dirInfo(base) |
| 120 | } |
| 121 | ents = append(ents, base) |
| 122 | fim[base] = fi |
| 123 | } |
| 124 | } |
| 125 | if dir == "/" { |
| 126 | break |
| 127 | } else { |
| 128 | isFile = false |
| 129 | lastBase = pathpkg.Base(dir) |
| 130 | dir = pathpkg.Dir(dir) |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | if len(ents) == 0 { |
| 135 | return nil, os.ErrNotExist |
| 136 | } |
| 137 | |
| 138 | sort.Strings(ents) |
| 139 | var list []os.FileInfo |
| 140 | for _, dir := range ents { |
| 141 | list = append(list, fim[dir]) |
| 142 | } |
| 143 | return list, nil |
| 144 | } |
| 145 | |
| 146 | // mapFI is the map-based implementation of FileInfo. |
| 147 | type mapFI struct { |
| 148 | name string |
| 149 | size int |
| 150 | dir bool |
| 151 | } |
| 152 | |
| 153 | func (fi mapFI) IsDir() bool { return fi.dir } |
| 154 | func (fi mapFI) ModTime() time.Time { return time.Time{} } |
| 155 | func (fi mapFI) Mode() os.FileMode { |
| 156 | if fi.IsDir() { |
| 157 | return 0755 | os.ModeDir |
| 158 | } |
| 159 | return 0444 |
| 160 | } |
| 161 | func (fi mapFI) Name() string { return pathpkg.Base(fi.name) } |
| 162 | func (fi mapFI) Size() int64 { return int64(fi.size) } |
| 163 | func (fi mapFI) Sys() interface{} { return nil } |
| 164 | |
| 165 | type nopCloser struct { |
| 166 | io.ReadSeeker |
| 167 | } |
| 168 | |
| 169 | func (nc nopCloser) Close() error { return nil } |
| 170 |
Members