-
Notifications
You must be signed in to change notification settings - Fork 53
Add wrapper for io/fs #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
b80c56f
db71dcb
28f6c49
b8c5b1b
b50bc97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Package iofs provides an adapter from billy.Filesystem to a the | ||
// standard library io.fs.FS interface. | ||
package iofs | ||
|
||
import ( | ||
"io" | ||
"io/fs" | ||
"path/filepath" | ||
|
||
billyfs "github.com/go-git/go-billy/v5" | ||
"github.com/go-git/go-billy/v5/helper/polyfill" | ||
) | ||
|
||
// Wrap adapts a billy.Filesystem to a io.fs.FS. | ||
func Wrap(fs billyfs.Basic) fs.FS { | ||
return &adapterFs{fs: polyfill.New(fs)} | ||
} | ||
|
||
type adapterFs struct { | ||
fs billyfs.Filesystem | ||
} | ||
|
||
var _ fs.FS = (*adapterFs)(nil) | ||
var _ fs.ReadDirFS = (*adapterFs)(nil) | ||
var _ fs.StatFS = (*adapterFs)(nil) | ||
var _ fs.ReadFileFS = (*adapterFs)(nil) | ||
pjbgf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// GlobFS would be harder, we don't implement for now. | ||
pjbgf marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
// Open implements fs.FS. | ||
evankanderson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
func (a *adapterFs) Open(name string) (fs.File, error) { | ||
if name[0] == '/' || name != filepath.Clean(name) { | ||
// fstest.TestFS explicitly checks that these should return error | ||
// MemFS is performs the clean internally, so we need to block that here for testing. | ||
evankanderson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} | ||
} | ||
stat, err := a.fs.Stat(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if stat.IsDir() { | ||
entries, err := a.ReadDir(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return makeDir(stat, entries), nil | ||
} | ||
file, err := a.fs.Open(name) | ||
return &adapterFile{file: file, info: stat}, err | ||
} | ||
|
||
// ReadDir implements fs.ReadDirFS. | ||
evankanderson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
func (a *adapterFs) ReadDir(name string) ([]fs.DirEntry, error) { | ||
items, err := a.fs.ReadDir(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
entries := make([]fs.DirEntry, len(items)) | ||
for i, item := range items { | ||
entries[i] = fs.FileInfoToDirEntry(item) | ||
} | ||
return entries, nil | ||
} | ||
|
||
// Stat implements fs.StatFS. | ||
func (a *adapterFs) Stat(name string) (fs.FileInfo, error) { | ||
return a.fs.Stat(name) | ||
} | ||
|
||
// ReadFile implements fs.ReadFileFS. | ||
func (a *adapterFs) ReadFile(name string) ([]byte, error) { | ||
stat, err := a.fs.Stat(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
b := make([]byte, stat.Size()) | ||
file, err := a.Open(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer file.Close() | ||
_, err = file.Read(b) | ||
return b, err | ||
} | ||
|
||
type adapterFile struct { | ||
file billyfs.File | ||
info fs.FileInfo | ||
} | ||
|
||
var _ fs.File = (*adapterFile)(nil) | ||
|
||
// Close implements fs.File. | ||
func (a *adapterFile) Close() error { | ||
return a.file.Close() | ||
} | ||
|
||
// Read implements fs.File. | ||
func (a *adapterFile) Read(b []byte) (int, error) { | ||
return a.file.Read(b) | ||
} | ||
|
||
// Stat implements fs.File. | ||
func (a *adapterFile) Stat() (fs.FileInfo, error) { | ||
return a.info, nil | ||
} | ||
|
||
type adapterDirFile struct { | ||
adapterFile | ||
entries []fs.DirEntry | ||
} | ||
|
||
var _ fs.ReadDirFile = (*adapterDirFile)(nil) | ||
|
||
func makeDir(stat fs.FileInfo, entries []fs.DirEntry) *adapterDirFile { | ||
return &adapterDirFile{ | ||
adapterFile: adapterFile{info: stat}, | ||
entries: entries, | ||
} | ||
} | ||
|
||
// Close implements fs.File. | ||
// Subtle: note that this is shadowing adapterFile.Close. | ||
func (a *adapterDirFile) Close() error { | ||
return nil | ||
} | ||
|
||
// ReadDir implements fs.ReadDirFile. | ||
func (a *adapterDirFile) ReadDir(n int) ([]fs.DirEntry, error) { | ||
if len(a.entries) == 0 && n > 0 { | ||
return nil, io.EOF | ||
} | ||
if n <= 0 { | ||
n = len(a.entries) | ||
} | ||
if n > len(a.entries) { | ||
evankanderson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
n = len(a.entries) | ||
} | ||
entries := a.entries[:n] | ||
a.entries = a.entries[n:] | ||
return entries, nil | ||
} | ||
evankanderson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,152 @@ | ||||||
package iofs | ||||||
|
||||||
import ( | ||||||
"errors" | ||||||
"io/fs" | ||||||
"path/filepath" | ||||||
"runtime" | ||||||
"strings" | ||||||
"testing" | ||||||
"testing/fstest" | ||||||
|
||||||
billyfs "github.com/go-git/go-billy/v5" | ||||||
"github.com/go-git/go-billy/v5/memfs" | ||||||
) | ||||||
|
||||||
type errorList interface { | ||||||
Unwrap() []error | ||||||
} | ||||||
|
||||||
type wrappedError interface { | ||||||
Unwrap() error | ||||||
} | ||||||
|
||||||
// TestWithFSTest leverages the packaged Go fstest package, which seems comprehensive | ||||||
func TestWithFSTest(t *testing.T) { | ||||||
t.Parallel() | ||||||
memfs := memfs.New() | ||||||
iofs := Wrap(memfs) | ||||||
|
||||||
files := map[string]string{ | ||||||
"foo.txt": "hello, world", | ||||||
"bar.txt": "goodbye, world", | ||||||
"dir/baz.txt": "こんにちわ, world", | ||||||
|
"dir/baz.txt": "こんにちわ, world", | |
filepath.Join("dir","baz.txt"): "こんにちわ, world", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out that fstest.testFS
isn't written in a Windows-aware way (e.g. https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/testing/fstest/testfs.go;l=147), so I skipped this test on Windows for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes me sad, but not sad enough to re-implement fstest.testFS in this file, given that we have Linux coverage and billy coverage.
Uh oh!
There was an error while loading. Please reload this page.