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