Skip to content

Commit 9b1db43

Browse files
feat: implement stat
1 parent 2b6ba40 commit 9b1db43

File tree

2 files changed

+75
-21
lines changed

2 files changed

+75
-21
lines changed

memfs.go

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
)
1212

1313
var _ fs.FS = FS(nil)
14+
var _ fs.StatFS = FS(nil)
15+
var _ fs.ReadDirFS = FS(nil)
1416

1517
type FS map[string]Entry
1618

@@ -21,11 +23,7 @@ func (f FS) Open(name string) (fs.File, error) {
2123
}
2224

2325
// Normalize the path
24-
trimmed := path.Clean(name)
25-
trimmed = strings.Trim(trimmed, "/")
26-
if trimmed == "." {
27-
trimmed = ""
28-
}
26+
trimmed := normalizePath(name)
2927

3028
// Find the entry with the pathname
3129
entry, ok := f[trimmed]
@@ -40,26 +38,46 @@ func (f FS) Open(name string) (fs.File, error) {
4038
}
4139

4240
// Wrap the file in a struct that implements the fs.File interface
43-
basename := path.Base(trimmed)
41+
basename := path.Base(name)
4442
return &inMemFile{
4543
reader: io.NopCloser(bytes.NewReader(fileEntry)),
4644
entry: fileEntry.ToEntry(basename),
4745
}, nil
4846
}
4947

50-
func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
48+
func (f FS) Stat(name string) (fs.FileInfo, error) {
5149
// If the map is nil, return an error
5250
if f == nil {
5351
return nil, fs.ErrNotExist
5452
}
5553

5654
// Normalize the path
57-
trimmed := path.Clean(name)
58-
trimmed = strings.Trim(trimmed, "/")
59-
if trimmed == "." {
60-
trimmed = ""
55+
trimmed := normalizePath(name)
56+
basename := path.Base(trimmed)
57+
58+
// Find the entry with the pathname
59+
entry, ok := f[trimmed]
60+
if ok {
61+
return entry.ToEntry(basename).Info()
62+
}
63+
64+
// Check if there are any child entries
65+
entries := f.getEntriesInDir(trimmed)
66+
if len(entries) > 0 {
67+
return Dir{}.ToEntry(basename).Info()
68+
}
69+
return nil, fs.ErrNotExist
70+
}
71+
72+
func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
73+
// If the map is nil, return an error
74+
if f == nil {
75+
return nil, fs.ErrNotExist
6176
}
6277

78+
// Normalize the path
79+
trimmed := normalizePath(name)
80+
6381
// Check if there is an empty directory entry at the path
6482
entryAtPath, hasEntry := f[trimmed]
6583
if hasEntry {
@@ -69,6 +87,23 @@ func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
6987
}
7088
}
7189

90+
// Get all the child entries of this path
91+
entries := f.getEntriesInDir(trimmed)
92+
93+
// If there are no entries, and also no empty dir nodes, return an error
94+
if len(entries) == 0 && !hasEntry && trimmed != "" {
95+
return nil, fs.ErrNotExist
96+
}
97+
98+
// Sort the entries by name
99+
slices.SortFunc(entries, func(a, b fs.DirEntry) int {
100+
return strings.Compare(a.Name(), b.Name())
101+
})
102+
103+
return entries, nil
104+
}
105+
106+
func (f FS) getEntriesInDir(trimmed string) []fs.DirEntry {
72107
// Look for all the entries that are children of the dir path
73108
var entries []fs.DirEntry
74109
intermediateDirs := make(map[string]struct{})
@@ -93,16 +128,14 @@ func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
93128
// Add the entry to the list of entries
94129
entries = append(entries, entry.ToEntry(suffix))
95130
}
131+
return entries
132+
}
96133

97-
// If there are no entries, and also no empty dir nodes, return an error
98-
if len(entries) == 0 && !hasEntry && trimmed != "" {
99-
return nil, fs.ErrNotExist
134+
func normalizePath(name string) string {
135+
trimmed := path.Clean(name)
136+
trimmed = strings.Trim(trimmed, "/")
137+
if trimmed == "." {
138+
trimmed = ""
100139
}
101-
102-
// Sort the entries by name
103-
slices.SortFunc(entries, func(a, b fs.DirEntry) int {
104-
return strings.Compare(a.Name(), b.Name())
105-
})
106-
107-
return entries, nil
140+
return trimmed
108141
}

memfs_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,27 @@ func TestMemFS(t *testing.T) {
184184
require.NoError(t, err, "reading dir")
185185
require.ElementsMatch(t, []string{"a"}, entryNames(entries), "reading dir")
186186
})
187+
t.Run("stat files and directories", func(t *testing.T) {
188+
fsys := memfs.FS{
189+
"hello/foo/a": memfs.File("helloworld"),
190+
"hello/foo/bar/baz": memfs.Dir{},
191+
}
192+
193+
stat, err := fsys.Stat("hello/foo/a")
194+
require.NoError(t, err, "stat file")
195+
require.Equal(t, "a", stat.Name(), "stat file name")
196+
require.Equal(t, false, stat.IsDir(), "stat file is not dir")
197+
require.Equal(t, int64(10), stat.Size(), "stat file size")
198+
199+
stat, err = fsys.Stat("hello/foo/bar/baz")
200+
require.NoError(t, err, "stat dir")
201+
require.Equal(t, "baz", stat.Name(), "stat dir name")
202+
require.Equal(t, true, stat.IsDir(), "stat dir is dir")
203+
204+
stat, err = fsys.Stat("hello/foo/doesntexist")
205+
require.Error(t, err, "stat non-existent file")
206+
require.Nil(t, stat, "stat non-existent file")
207+
})
187208
}
188209

189210
func entryNames(entries []fs.DirEntry) []string {

0 commit comments

Comments
 (0)