7
7
"io/fs"
8
8
"net/http"
9
9
"os"
10
+ "path"
10
11
"path/filepath"
12
+ "strings"
11
13
"time"
12
14
)
13
15
@@ -124,17 +126,21 @@ type MemoryFileSystem struct {
124
126
// It comes with no files, use `ParseTemplate` to add new virtual/memory template files.
125
127
// Usage:
126
128
//
127
- // vfs := NewVirtualFileSystem()
128
- // err := vfs .ParseTemplate("example.html", []byte("Hello, World!"), nil)
129
- // templates := New(vfs )
129
+ // mfs := NewVirtualFileSystem()
130
+ // err := mfs .ParseTemplate("example.html", []byte("Hello, World!"), nil)
131
+ // templates := New(mfs )
130
132
// templates.Load()
131
133
func NewMemoryFileSystem () * MemoryFileSystem {
132
134
return & MemoryFileSystem {
133
135
files : make (map [string ]* memoryTemplateFile ),
134
136
}
135
137
}
136
138
137
- var _ fs.FS = (* MemoryFileSystem )(nil )
139
+ // Ensure MemoryFileSystem implements fs.FS, fs.ReadDirFS and fs.WalkDirFS interfaces.
140
+ var (
141
+ _ fs.FS = (* MemoryFileSystem )(nil )
142
+ _ fs.ReadDirFS = (* MemoryFileSystem )(nil )
143
+ )
138
144
139
145
// ParseTemplate adds a new memory temlate to the file system.
140
146
func (vfs * MemoryFileSystem ) ParseTemplate (name string , contents []byte , funcMap template.FuncMap ) error {
@@ -147,14 +153,178 @@ func (vfs *MemoryFileSystem) ParseTemplate(name string, contents []byte, funcMap
147
153
}
148
154
149
155
// Open implements the fs.FS interface.
150
- func (vfs * MemoryFileSystem ) Open (name string ) (fs.File , error ) {
151
- if file , exists := vfs .files [name ]; exists {
156
+ func (mfs * MemoryFileSystem ) Open (name string ) (fs.File , error ) {
157
+ if name == "." || name == "/" {
158
+ // Return a directory representing the root.
159
+ return & memoryDir {
160
+ fs : mfs ,
161
+ name : "." ,
162
+ }, nil
163
+ }
164
+
165
+ if mfs .isDir (name ) {
166
+ // Return a directory.
167
+ return & memoryDir {
168
+ fs : mfs ,
169
+ name : name ,
170
+ }, nil
171
+ }
172
+
173
+ if file , exists := mfs .files [name ]; exists {
152
174
file .reset () // Reset read position
153
175
return file , nil
154
176
}
177
+
155
178
return nil , fs .ErrNotExist
156
179
}
157
180
181
+ // ReadDir implements the fs.ReadDirFS interface.
182
+ func (mfs * MemoryFileSystem ) ReadDir (name string ) ([]fs.DirEntry , error ) {
183
+ var entries []fs.DirEntry
184
+ prefix := strings .TrimLeftFunc (name , func (r rune ) bool {
185
+ return r == '.' || r == '/'
186
+ })
187
+ if prefix != "" && ! strings .HasSuffix (prefix , "/" ) {
188
+ prefix += "/"
189
+ }
190
+
191
+ seen := make (map [string ]bool )
192
+
193
+ for path := range mfs .files {
194
+ if ! strings .HasPrefix (path , prefix ) {
195
+ continue
196
+ }
197
+
198
+ trimmedPath := strings .TrimPrefix (path , prefix )
199
+ parts := strings .SplitN (trimmedPath , "/" , 2 )
200
+ entryName := parts [0 ]
201
+
202
+ if seen [entryName ] {
203
+ continue
204
+ }
205
+ seen [entryName ] = true
206
+
207
+ fullPath := prefix + entryName
208
+ if mfs .isDir (fullPath ) {
209
+ info := & memoryDirInfo {name : entryName }
210
+ entries = append (entries , fs .FileInfoToDirEntry (info ))
211
+ } else {
212
+ file , _ := mfs .files [fullPath ]
213
+ info := & memoryFileInfo {
214
+ name : entryName ,
215
+ size : int64 (len (file .contents )),
216
+ }
217
+ entries = append (entries , fs .FileInfoToDirEntry (info ))
218
+ }
219
+ }
220
+
221
+ return entries , nil
222
+ }
223
+
224
+ // isDir checks if the given name is a directory in the memory file system.
225
+ func (mfs * MemoryFileSystem ) isDir (name string ) bool {
226
+ dirPrefix := name
227
+ if dirPrefix != "" && ! strings .HasSuffix (dirPrefix , "/" ) {
228
+ dirPrefix += "/"
229
+ }
230
+ for path := range mfs .files {
231
+ if strings .HasPrefix (path , dirPrefix ) {
232
+ return true
233
+ }
234
+ }
235
+ return false
236
+ }
237
+
238
+ type memoryDir struct {
239
+ fs * MemoryFileSystem
240
+ name string
241
+ offset int
242
+ entries []fs.DirEntry
243
+ }
244
+
245
+ // Ensure memoryDir implements fs.ReadDirFile interface.
246
+ var _ fs.ReadDirFile = (* memoryDir )(nil )
247
+
248
+ // Read implements the io.Reader interface.
249
+ func (d * memoryDir ) Read (p []byte ) (int , error ) {
250
+ return 0 , io .EOF // Directories cannot be read as files.
251
+ }
252
+
253
+ // Close implements the io.Closer interface.
254
+ func (d * memoryDir ) Close () error {
255
+ return nil
256
+ }
257
+
258
+ // Stat implements the fs.File interface.
259
+ func (d * memoryDir ) Stat () (fs.FileInfo , error ) {
260
+ return & memoryDirInfo {
261
+ name : d .name ,
262
+ }, nil
263
+ }
264
+
265
+ // ReadDir implements the fs.ReadDirFile interface.
266
+ func (d * memoryDir ) ReadDir (n int ) ([]fs.DirEntry , error ) {
267
+ if d .entries == nil {
268
+ // Initialize the entries slice.
269
+ entries , err := d .fs .ReadDir (d .name )
270
+ if err != nil {
271
+ return nil , err
272
+ }
273
+ d .entries = entries
274
+ }
275
+
276
+ if d .offset >= len (d .entries ) {
277
+ return nil , io .EOF
278
+ }
279
+
280
+ if n <= 0 || d .offset + n > len (d .entries ) {
281
+ n = len (d .entries ) - d .offset
282
+ }
283
+
284
+ entries := d .entries [d .offset : d .offset + n ]
285
+ d .offset += n
286
+
287
+ return entries , nil
288
+ }
289
+
290
+ // memoryDirInfo provides directory information for a memory directory.
291
+ type memoryDirInfo struct {
292
+ name string
293
+ }
294
+
295
+ // Ensure memoryDirInfo implements fs.FileInfo interface.
296
+ var _ fs.FileInfo = (* memoryDirInfo )(nil )
297
+
298
+ // Name returns the base name of the directory.
299
+ func (di * memoryDirInfo ) Name () string {
300
+ return di .name
301
+ }
302
+
303
+ // Size returns the length in bytes (zero for directories).
304
+ func (di * memoryDirInfo ) Size () int64 {
305
+ return 0
306
+ }
307
+
308
+ // Mode returns file mode bits.
309
+ func (di * memoryDirInfo ) Mode () fs.FileMode {
310
+ return fs .ModeDir | 0555 // Readable directory
311
+ }
312
+
313
+ // ModTime returns modification time.
314
+ func (di * memoryDirInfo ) ModTime () time.Time {
315
+ return time .Now ()
316
+ }
317
+
318
+ // IsDir reports if the file is a directory.
319
+ func (di * memoryDirInfo ) IsDir () bool {
320
+ return true
321
+ }
322
+
323
+ // Sys returns underlying data source (can return nil).
324
+ func (di * memoryDirInfo ) Sys () interface {} {
325
+ return nil
326
+ }
327
+
158
328
// memoryTemplateFile represents a memory file.
159
329
type memoryTemplateFile struct {
160
330
name string
@@ -166,30 +336,30 @@ type memoryTemplateFile struct {
166
336
// Ensure memoryTemplateFile implements fs.File interface.
167
337
var _ fs.File = (* memoryTemplateFile )(nil )
168
338
169
- func (vf * memoryTemplateFile ) reset () {
170
- vf .offset = 0
339
+ func (mf * memoryTemplateFile ) reset () {
340
+ mf .offset = 0
171
341
}
172
342
173
343
// Stat implements the fs.File interface, returning file info.
174
- func (vf * memoryTemplateFile ) Stat () (fs.FileInfo , error ) {
344
+ func (mf * memoryTemplateFile ) Stat () (fs.FileInfo , error ) {
175
345
return & memoryFileInfo {
176
- name : vf . name ,
177
- size : int64 (len (vf .contents )),
346
+ name : path . Base ( mf . name ) ,
347
+ size : int64 (len (mf .contents )),
178
348
}, nil
179
349
}
180
350
181
351
// Read implements the io.Reader interface.
182
- func (vf * memoryTemplateFile ) Read (p []byte ) (int , error ) {
183
- if vf .offset >= int64 (len (vf .contents )) {
352
+ func (mf * memoryTemplateFile ) Read (p []byte ) (int , error ) {
353
+ if mf .offset >= int64 (len (mf .contents )) {
184
354
return 0 , io .EOF
185
355
}
186
- n := copy (p , vf .contents [vf .offset :])
187
- vf .offset += int64 (n )
356
+ n := copy (p , mf .contents [mf .offset :])
357
+ mf .offset += int64 (n )
188
358
return n , nil
189
359
}
190
360
191
361
// Close implements the io.Closer interface.
192
- func (vf * memoryTemplateFile ) Close () error {
362
+ func (mf * memoryTemplateFile ) Close () error {
193
363
return nil
194
364
}
195
365
0 commit comments