iofs.go (6545B)
1 //go:build go1.16 2 // +build go1.16 3 4 package afero 5 6 import ( 7 "io" 8 "io/fs" 9 "os" 10 "path" 11 "sort" 12 "time" 13 14 "github.com/spf13/afero/internal/common" 15 ) 16 17 // IOFS adopts afero.Fs to stdlib io/fs.FS 18 type IOFS struct { 19 Fs 20 } 21 22 func NewIOFS(fs Fs) IOFS { 23 return IOFS{Fs: fs} 24 } 25 26 var ( 27 _ fs.FS = IOFS{} 28 _ fs.GlobFS = IOFS{} 29 _ fs.ReadDirFS = IOFS{} 30 _ fs.ReadFileFS = IOFS{} 31 _ fs.StatFS = IOFS{} 32 _ fs.SubFS = IOFS{} 33 ) 34 35 func (iofs IOFS) Open(name string) (fs.File, error) { 36 const op = "open" 37 38 // by convention for fs.FS implementations we should perform this check 39 if !fs.ValidPath(name) { 40 return nil, iofs.wrapError(op, name, fs.ErrInvalid) 41 } 42 43 file, err := iofs.Fs.Open(name) 44 if err != nil { 45 return nil, iofs.wrapError(op, name, err) 46 } 47 48 // file should implement fs.ReadDirFile 49 if _, ok := file.(fs.ReadDirFile); !ok { 50 file = readDirFile{file} 51 } 52 53 return file, nil 54 } 55 56 func (iofs IOFS) Glob(pattern string) ([]string, error) { 57 const op = "glob" 58 59 // afero.Glob does not perform this check but it's required for implementations 60 if _, err := path.Match(pattern, ""); err != nil { 61 return nil, iofs.wrapError(op, pattern, err) 62 } 63 64 items, err := Glob(iofs.Fs, pattern) 65 if err != nil { 66 return nil, iofs.wrapError(op, pattern, err) 67 } 68 69 return items, nil 70 } 71 72 func (iofs IOFS) ReadDir(name string) ([]fs.DirEntry, error) { 73 f, err := iofs.Fs.Open(name) 74 if err != nil { 75 return nil, iofs.wrapError("readdir", name, err) 76 } 77 78 defer f.Close() 79 80 if rdf, ok := f.(fs.ReadDirFile); ok { 81 items, err := rdf.ReadDir(-1) 82 if err != nil { 83 return nil, iofs.wrapError("readdir", name, err) 84 } 85 sort.Slice(items, func(i, j int) bool { return items[i].Name() < items[j].Name() }) 86 return items, nil 87 } 88 89 items, err := f.Readdir(-1) 90 if err != nil { 91 return nil, iofs.wrapError("readdir", name, err) 92 } 93 sort.Sort(byName(items)) 94 95 ret := make([]fs.DirEntry, len(items)) 96 for i := range items { 97 ret[i] = common.FileInfoDirEntry{FileInfo: items[i]} 98 } 99 100 return ret, nil 101 } 102 103 func (iofs IOFS) ReadFile(name string) ([]byte, error) { 104 const op = "readfile" 105 106 if !fs.ValidPath(name) { 107 return nil, iofs.wrapError(op, name, fs.ErrInvalid) 108 } 109 110 bytes, err := ReadFile(iofs.Fs, name) 111 if err != nil { 112 return nil, iofs.wrapError(op, name, err) 113 } 114 115 return bytes, nil 116 } 117 118 func (iofs IOFS) Sub(dir string) (fs.FS, error) { return IOFS{NewBasePathFs(iofs.Fs, dir)}, nil } 119 120 func (IOFS) wrapError(op, path string, err error) error { 121 if _, ok := err.(*fs.PathError); ok { 122 return err // don't need to wrap again 123 } 124 125 return &fs.PathError{ 126 Op: op, 127 Path: path, 128 Err: err, 129 } 130 } 131 132 // readDirFile provides adapter from afero.File to fs.ReadDirFile needed for correct Open 133 type readDirFile struct { 134 File 135 } 136 137 var _ fs.ReadDirFile = readDirFile{} 138 139 func (r readDirFile) ReadDir(n int) ([]fs.DirEntry, error) { 140 items, err := r.File.Readdir(n) 141 if err != nil { 142 return nil, err 143 } 144 145 ret := make([]fs.DirEntry, len(items)) 146 for i := range items { 147 ret[i] = common.FileInfoDirEntry{FileInfo: items[i]} 148 } 149 150 return ret, nil 151 } 152 153 // FromIOFS adopts io/fs.FS to use it as afero.Fs 154 // Note that io/fs.FS is read-only so all mutating methods will return fs.PathError with fs.ErrPermission 155 // To store modifications you may use afero.CopyOnWriteFs 156 type FromIOFS struct { 157 fs.FS 158 } 159 160 var _ Fs = FromIOFS{} 161 162 func (f FromIOFS) Create(name string) (File, error) { return nil, notImplemented("create", name) } 163 164 func (f FromIOFS) Mkdir(name string, perm os.FileMode) error { return notImplemented("mkdir", name) } 165 166 func (f FromIOFS) MkdirAll(path string, perm os.FileMode) error { 167 return notImplemented("mkdirall", path) 168 } 169 170 func (f FromIOFS) Open(name string) (File, error) { 171 file, err := f.FS.Open(name) 172 if err != nil { 173 return nil, err 174 } 175 176 return fromIOFSFile{File: file, name: name}, nil 177 } 178 179 func (f FromIOFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 180 return f.Open(name) 181 } 182 183 func (f FromIOFS) Remove(name string) error { 184 return notImplemented("remove", name) 185 } 186 187 func (f FromIOFS) RemoveAll(path string) error { 188 return notImplemented("removeall", path) 189 } 190 191 func (f FromIOFS) Rename(oldname, newname string) error { 192 return notImplemented("rename", oldname) 193 } 194 195 func (f FromIOFS) Stat(name string) (os.FileInfo, error) { return fs.Stat(f.FS, name) } 196 197 func (f FromIOFS) Name() string { return "fromiofs" } 198 199 func (f FromIOFS) Chmod(name string, mode os.FileMode) error { 200 return notImplemented("chmod", name) 201 } 202 203 func (f FromIOFS) Chown(name string, uid, gid int) error { 204 return notImplemented("chown", name) 205 } 206 207 func (f FromIOFS) Chtimes(name string, atime time.Time, mtime time.Time) error { 208 return notImplemented("chtimes", name) 209 } 210 211 type fromIOFSFile struct { 212 fs.File 213 name string 214 } 215 216 func (f fromIOFSFile) ReadAt(p []byte, off int64) (n int, err error) { 217 readerAt, ok := f.File.(io.ReaderAt) 218 if !ok { 219 return -1, notImplemented("readat", f.name) 220 } 221 222 return readerAt.ReadAt(p, off) 223 } 224 225 func (f fromIOFSFile) Seek(offset int64, whence int) (int64, error) { 226 seeker, ok := f.File.(io.Seeker) 227 if !ok { 228 return -1, notImplemented("seek", f.name) 229 } 230 231 return seeker.Seek(offset, whence) 232 } 233 234 func (f fromIOFSFile) Write(p []byte) (n int, err error) { 235 return -1, notImplemented("write", f.name) 236 } 237 238 func (f fromIOFSFile) WriteAt(p []byte, off int64) (n int, err error) { 239 return -1, notImplemented("writeat", f.name) 240 } 241 242 func (f fromIOFSFile) Name() string { return f.name } 243 244 func (f fromIOFSFile) Readdir(count int) ([]os.FileInfo, error) { 245 rdfile, ok := f.File.(fs.ReadDirFile) 246 if !ok { 247 return nil, notImplemented("readdir", f.name) 248 } 249 250 entries, err := rdfile.ReadDir(count) 251 if err != nil { 252 return nil, err 253 } 254 255 ret := make([]os.FileInfo, len(entries)) 256 for i := range entries { 257 ret[i], err = entries[i].Info() 258 259 if err != nil { 260 return nil, err 261 } 262 } 263 264 return ret, nil 265 } 266 267 func (f fromIOFSFile) Readdirnames(n int) ([]string, error) { 268 rdfile, ok := f.File.(fs.ReadDirFile) 269 if !ok { 270 return nil, notImplemented("readdir", f.name) 271 } 272 273 entries, err := rdfile.ReadDir(n) 274 if err != nil { 275 return nil, err 276 } 277 278 ret := make([]string, len(entries)) 279 for i := range entries { 280 ret[i] = entries[i].Name() 281 } 282 283 return ret, nil 284 } 285 286 func (f fromIOFSFile) Sync() error { return nil } 287 288 func (f fromIOFSFile) Truncate(size int64) error { 289 return notImplemented("truncate", f.name) 290 } 291 292 func (f fromIOFSFile) WriteString(s string) (ret int, err error) { 293 return -1, notImplemented("writestring", f.name) 294 } 295 296 func notImplemented(op, path string) error { 297 return &fs.PathError{Op: op, Path: path, Err: fs.ErrPermission} 298 }