Skip to content

Commit

Permalink
Add adapter to allow afero.Fs usage as io/fs.FS
Browse files Browse the repository at this point in the history
It's made as separated package to not enforce go 1.16+ usage.
  • Loading branch information
xakep666 committed Mar 13, 2021
1 parent 32b5faa commit b579aaf
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 0 deletions.
135 changes: 135 additions & 0 deletions iofs/iofs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Package iofs provides adapter for afero usage with stdlib io/fs
package iofs

import (
"io/fs"
"path"

"github.com/spf13/afero"
)

// IOFS adopts afero.Fs to stdlib io/fs.FS
type IOFS struct {
afero.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 := afero.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 := afero.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 := afero.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{afero.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 {
afero.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
}
54 changes: 54 additions & 0 deletions iofs/iofs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package iofs_test

import (
"os"
"testing"
"testing/fstest"

"github.com/spf13/afero"
"github.com/spf13/afero/iofs"
)

func TestIOFS(t *testing.T) {
t.Parallel()

t.Run("use MemMapFs", func(t *testing.T) {
mmfs := afero.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(iofs.IOFS{Fs: mmfs}, "dir1/dir2/test.txt"); err != nil {
t.Error(err)
}
})

t.Run("use OsFs", func(t *testing.T) {
osfs := afero.NewBasePathFs(afero.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(iofs.IOFS{Fs: osfs}, "dir1/dir2/test.txt"); err != nil {
t.Error(err)
}
})
}

0 comments on commit b579aaf

Please sign in to comment.