@@ -28,11 +28,26 @@ var _ allFS = &FS{}
28
28
// FS is an in-memory filesystem
29
29
type FS struct {
30
30
root * file
31
+
32
+ // When the underlyingRoot has a value, it allows access to the local filesystem outside of this in-memory filesystem.
33
+ // The set path is used as the starting point when accessing the local filesystem.
34
+ // In other words, although mapfs.Open("../foo") would normally result in an error, if this option is enabled,
35
+ // it will be executed as os.Open(filepath.Join(underlyingRoot, "../foo")).
36
+ underlyingRoot string
37
+ }
38
+
39
+ type Option func (* FS )
40
+
41
+ // WithUnderlyingRoot returns an option to set the underlying root path for the in-memory filesystem.
42
+ func WithUnderlyingRoot (root string ) Option {
43
+ return func (fsys * FS ) {
44
+ fsys .underlyingRoot = root
45
+ }
31
46
}
32
47
33
48
// New creates a new filesystem
34
- func New () * FS {
35
- return & FS {
49
+ func New (opts ... Option ) * FS {
50
+ fsys := & FS {
36
51
root : & file {
37
52
stat : fileStat {
38
53
name : "." ,
@@ -43,6 +58,10 @@ func New() *FS {
43
58
files : syncx.Map [string , * file ]{},
44
59
},
45
60
}
61
+ for _ , opt := range opts {
62
+ opt (fsys )
63
+ }
64
+ return fsys
46
65
}
47
66
48
67
// Filter removes the specified skippedFiles and returns a new FS
@@ -57,7 +76,7 @@ func (m *FS) Filter(skippedFiles []string) (*FS, error) {
57
76
}
58
77
59
78
func (m * FS ) FilterFunc (fn func (path string , d fs.DirEntry ) (bool , error )) (* FS , error ) {
60
- newFS := New ()
79
+ newFS := New (WithUnderlyingRoot ( m . underlyingRoot ) )
61
80
err := fs .WalkDir (m , "." , func (path string , d fs.DirEntry , err error ) error {
62
81
if err != nil {
63
82
return err
@@ -103,6 +122,10 @@ func (m *FS) CopyFilesUnder(dir string) error {
103
122
104
123
// Stat returns a FileInfo describing the file.
105
124
func (m * FS ) Stat (name string ) (fs.FileInfo , error ) {
125
+ if strings .HasPrefix (name , "../" ) && m .underlyingRoot != "" {
126
+ return os .Stat (filepath .Join (m .underlyingRoot , name ))
127
+ }
128
+
106
129
name = cleanPath (name )
107
130
f , err := m .root .getFile (name )
108
131
if err != nil {
@@ -121,11 +144,17 @@ func (m *FS) Stat(name string) (fs.FileInfo, error) {
121
144
// ReadDir reads the named directory
122
145
// and returns a list of directory entries sorted by filename.
123
146
func (m * FS ) ReadDir (name string ) ([]fs.DirEntry , error ) {
147
+ if strings .HasPrefix (name , "../" ) && m .underlyingRoot != "" {
148
+ return os .ReadDir (filepath .Join (m .underlyingRoot , name ))
149
+ }
124
150
return m .root .ReadDir (cleanPath (name ))
125
151
}
126
152
127
153
// Open opens the named file for reading.
128
154
func (m * FS ) Open (name string ) (fs.File , error ) {
155
+ if strings .HasPrefix (name , "../" ) && m .underlyingRoot != "" {
156
+ return os .Open (filepath .Join (m .underlyingRoot , name ))
157
+ }
129
158
return m .root .Open (cleanPath (name ))
130
159
}
131
160
@@ -158,6 +187,10 @@ func (m *FS) MkdirAll(path string, perm fs.FileMode) error {
158
187
// The caller is permitted to modify the returned byte slice.
159
188
// This method should return a copy of the underlying data.
160
189
func (m * FS ) ReadFile (name string ) ([]byte , error ) {
190
+ if strings .HasPrefix (name , "../" ) && m .underlyingRoot != "" {
191
+ return os .ReadFile (filepath .Join (m .underlyingRoot , name ))
192
+ }
193
+
161
194
f , err := m .root .Open (cleanPath (name ))
162
195
if err != nil {
163
196
return nil , err
0 commit comments