-
Notifications
You must be signed in to change notification settings - Fork 315
/
vfs.go
225 lines (196 loc) · 4.7 KB
/
vfs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package vfs
import (
"errors"
"io/fs"
"path/filepath"
"strings"
"time"
)
type VFS struct {
root *directoryContents
}
var (
_ fs.FS = (*VFS)(nil)
_ fs.ReadDirFS = (*VFS)(nil)
_ fs.ReadFileFS = (*VFS)(nil)
_ fs.SubFS = (*VFS)(nil)
_ fs.StatFS = (*VFS)(nil)
)
func New() *VFS {
return &VFS{root: newDirectoryContents("")}
}
// Open opens the named file.
//
// When Open returns an error, it should be of type *PathError
// with the Op field set to "open", the Path field set to name,
// and the Err field describing the problem.
//
// Open should reject attempts to open names that do not satisfy
// ValidPath(name), returning a *PathError with Err set to
// ErrInvalid or ErrNotExist.
func (v *VFS) Open(name string) (fs.File, error) {
if name == "." {
return &Directory{directoryContents: v.root}, nil
}
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrInvalid,
}
}
parts := strings.Split(name, "/")
pathTravelled := make([]string, 0, len(parts))
dir := v.root
pathTravelled = append(pathTravelled, dir.name)
if len(parts) > 1 {
for _, subDir := range parts[:len(parts)-1] {
pathTravelled = append(pathTravelled, subDir)
dir = dir.childDirs[subDir]
if dir == nil {
return nil, &fs.PathError{
Op: "open",
Path: strings.Join(pathTravelled, "/"),
Err: fs.ErrNotExist,
}
}
}
}
// Return the child
childName := parts[len(parts)-1]
if childFile, found := dir.files[childName]; found {
return &File{fileContents: childFile}, nil
}
if childDir, found := dir.childDirs[childName]; found {
return &Directory{directoryContents: childDir}, nil
}
return nil, &fs.PathError{
Op: "open",
Path: strings.Join(pathTravelled, "/"),
Err: fs.ErrNotExist,
}
}
// ReadDir reads the named directory
// and returns a list of directory entries sorted by filename.
func (v *VFS) ReadDir(name string) ([]fs.DirEntry, error) {
file, err := v.Open(name)
if err != nil {
return nil, &fs.PathError{
Op: "ReadDir",
Path: name,
Err: err,
}
}
switch file := file.(type) {
case *Directory:
return file.createEntries(), nil
default:
return nil, &fs.PathError{
Op: "ReadDir",
Path: name,
Err: errors.New("not a directory"),
}
}
}
// ReadFile reads the named file and returns its contents.
// A successful call returns a nil error, not io.EOF.
// (Because ReadFile reads the whole file, the expected EOF
// from the final Read is not treated as an error to be reported.)
//
// The caller is permitted to modify the returned byte slice.
// This method should return a copy of the underlying data.
func (v *VFS) ReadFile(name string) ([]byte, error) {
file, err := v.Open(name)
if err != nil {
return nil, &fs.PathError{
Op: "ReadFile",
Path: name,
Err: err,
}
}
switch file := file.(type) {
case *File:
rtnBytes := make([]byte, len(file.contents))
copy(rtnBytes, file.contents)
return rtnBytes, nil
default:
return nil, &fs.PathError{
Op: "ReadFile",
Path: name,
Err: errors.New("not a file"),
}
}
}
// Sub returns an FS corresponding to the subtree rooted at dir.
func (v *VFS) Sub(dir string) (fs.FS, error) {
file, err := v.Open(dir)
if err != nil {
return nil, &fs.PathError{
Op: "Sub",
Path: dir,
Err: err,
}
}
switch file := file.(type) {
case *Directory:
return &VFS{root: file.directoryContents}, nil
default:
return nil, &fs.PathError{
Op: "Sub",
Path: dir,
Err: errors.New("not a directory"),
}
}
}
// Stat returns a FileInfo describing the file.
// If there is an error, it should be of type *PathError.
func (v *VFS) Stat(name string) (fs.FileInfo, error) {
file, err := v.Open(name)
if err != nil {
return nil, &fs.PathError{
Op: "Stat",
Path: name,
Err: err,
}
}
return file.Stat()
}
// AddFile records a file into the VFS
func (v *VFS) AddFile(path string, bytes []byte, time time.Time) (*fileContents, error) {
dirPath, filename := filepath.Split(path)
dir := v.AddDir(dirPath)
dir.files[filename] = &fileContents{
node: node{
name: filename,
parent: dir,
modTime: time,
},
contents: bytes,
}
return dir.files[filename], nil
}
// AddDir records a directory into the VFS.
func (v *VFS) AddDir(dirPath string) *directoryContents {
dirParts := strings.Split(dirPath, "/")
dir := v.root
for _, dirPart := range dirParts {
if dirPart == "." || dirPart == "" {
continue
}
if dirPart == ".." {
if dir.node.parent == nil {
return nil
}
dir = dir.node.parent
continue
}
child := dir.childDirs[dirPart]
if child == nil {
child = newDirectoryContents(dirPart)
child.node.parent = dir
dir.childDirs[dirPart] = child
}
dir = child
}
return dir
}