forked from mirror/afero
Add adapter to allow afero.Fs usage as io/fs.FS
It will not affect users of go older than 1.16 because files with `io/fs` import marked with build constraints
This commit is contained in:
parent
32b5faae5b
commit
c597fe5aa8
|
@ -7,6 +7,7 @@ arch:
|
||||||
go:
|
go:
|
||||||
- "1.14"
|
- "1.14"
|
||||||
- "1.15"
|
- "1.15"
|
||||||
|
- "1.16"
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
os:
|
os:
|
||||||
|
|
|
@ -33,7 +33,7 @@ filesystem for full interoperability.
|
||||||
* Support for compositional (union) file systems by combining multiple file systems acting as one
|
* Support for compositional (union) file systems by combining multiple file systems acting as one
|
||||||
* Specialized backends which modify existing filesystems (Read Only, Regexp filtered)
|
* Specialized backends which modify existing filesystems (Read Only, Regexp filtered)
|
||||||
* A set of utility functions ported from io, ioutil & hugo to be afero aware
|
* A set of utility functions ported from io, ioutil & hugo to be afero aware
|
||||||
|
* Wrapper for go 1.16 filesystem abstraction `io/fs.FS`
|
||||||
|
|
||||||
# Using Afero
|
# Using Afero
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
// +build go1.16
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IOFS adopts afero.Fs to stdlib io/fs.FS
|
||||||
|
type IOFS struct {
|
||||||
|
Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIOFS(fs Fs) IOFS {
|
||||||
|
return IOFS{Fs: fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ fs.FS = IOFS{}
|
||||||
|
_ fs.GlobFS = IOFS{}
|
||||||
|
_ fs.ReadDirFS = IOFS{}
|
||||||
|
_ fs.ReadFileFS = IOFS{}
|
||||||
|
_ fs.StatFS = IOFS{}
|
||||||
|
_ fs.SubFS = IOFS{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (iofs IOFS) Open(name string) (fs.File, error) {
|
||||||
|
const op = "open"
|
||||||
|
|
||||||
|
// by convention for fs.FS implementations we should perform this check
|
||||||
|
if !fs.ValidPath(name) {
|
||||||
|
return nil, iofs.wrapError(op, name, fs.ErrInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := iofs.Fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, iofs.wrapError(op, name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// file should implement fs.ReadDirFile
|
||||||
|
if _, ok := file.(fs.ReadDirFile); !ok {
|
||||||
|
file = readDirFile{file}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iofs IOFS) Glob(pattern string) ([]string, error) {
|
||||||
|
const op = "glob"
|
||||||
|
|
||||||
|
// afero.Glob does not perform this check but it's required for implementations
|
||||||
|
if _, err := path.Match(pattern, ""); err != nil {
|
||||||
|
return nil, iofs.wrapError(op, pattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := Glob(iofs.Fs, pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, iofs.wrapError(op, pattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iofs IOFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||||
|
items, err := ReadDir(iofs.Fs, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, iofs.wrapError("readdir", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]fs.DirEntry, len(items))
|
||||||
|
for i := range items {
|
||||||
|
ret[i] = dirEntry{items[i]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iofs IOFS) ReadFile(name string) ([]byte, error) {
|
||||||
|
const op = "readfile"
|
||||||
|
|
||||||
|
if !fs.ValidPath(name) {
|
||||||
|
return nil, iofs.wrapError(op, name, fs.ErrInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := ReadFile(iofs.Fs, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, iofs.wrapError(op, name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iofs IOFS) Sub(dir string) (fs.FS, error) { return IOFS{NewBasePathFs(iofs.Fs, dir)}, nil }
|
||||||
|
|
||||||
|
func (IOFS) wrapError(op, path string, err error) error {
|
||||||
|
if _, ok := err.(*fs.PathError); ok {
|
||||||
|
return err // don't need to wrap again
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fs.PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirEntry provides adapter from os.FileInfo to fs.DirEntry
|
||||||
|
type dirEntry struct {
|
||||||
|
fs.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fs.DirEntry = dirEntry{}
|
||||||
|
|
||||||
|
func (d dirEntry) Type() fs.FileMode { return d.FileInfo.Mode().Type() }
|
||||||
|
|
||||||
|
func (d dirEntry) Info() (fs.FileInfo, error) { return d.FileInfo, nil }
|
||||||
|
|
||||||
|
// readDirFile provides adapter from afero.File to fs.ReadDirFile needed for correct Open
|
||||||
|
type readDirFile struct {
|
||||||
|
File
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fs.ReadDirFile = readDirFile{}
|
||||||
|
|
||||||
|
func (r readDirFile) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||||
|
items, err := r.File.Readdir(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]fs.DirEntry, len(items))
|
||||||
|
for i := range items {
|
||||||
|
ret[i] = dirEntry{items[i]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
// +build go1.16
|
||||||
|
|
||||||
|
package afero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIOFS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("use MemMapFs", func(t *testing.T) {
|
||||||
|
mmfs := NewMemMapFs()
|
||||||
|
|
||||||
|
err := mmfs.MkdirAll("dir1/dir2", os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("MkdirAll failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := mmfs.OpenFile("dir1/dir2/test.txt", os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("OpenFile (O_CREATE) failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
if err := fstest.TestFS(NewIOFS(mmfs), "dir1/dir2/test.txt"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("use OsFs", func(t *testing.T) {
|
||||||
|
osfs := NewBasePathFs(NewOsFs(), t.TempDir())
|
||||||
|
|
||||||
|
err := osfs.MkdirAll("dir1/dir2", os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("MkdirAll failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := osfs.OpenFile("dir1/dir2/test.txt", os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("OpenFile (O_CREATE) failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
if err := fstest.TestFS(NewIOFS(osfs), "dir1/dir2/test.txt"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue