diff --git a/README.md b/README.md index 96885a8..a6c298c 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,9 @@ [![Report Card](https://goreportcard.com/badge/github.com/jarxorg/io2)](https://goreportcard.com/report/github.com/jarxorg/io2) [![Coverage Status](https://coveralls.io/repos/github/jarxorg/io2/badge.svg?branch=main)](https://coveralls.io/github/jarxorg/io2?branch=main) -Go "io" and "io/fs" package utilities. +Go "io" package utilities. -## Writable io/fs.FS implementations - -- [osfs](https://github.com/jarxorg/io2/tree/main/osfs) -- [memfs](https://github.com/jarxorg/io2/tree/main/memfs) -- [s3fs](https://github.com/jarxorg/s3fs) - -```go -package main - -import ( - "fmt" - "io/fs" - "log" - - "github.com/jarxorg/io2" - "github.com/jarxorg/io2/memfs" - "github.com/jarxorg/io2/osfs" -) - -func main() { - osFsys := osfs.DirFS(".") - memFsys := memfs.New() - - err := io2.CopyFS(memFsys, osFsys, "osfs/testdata") - if err != nil { - log.Fatal(err) - } - - names, err := fs.Glob(memFsys, "osfs/testdata/dir0/*.txt") - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%v\n", names) - - // Output: [osfs/testdata/dir0/file01.txt osfs/testdata/dir0/file02.txt] -} -``` +NOTE: some codes moves to [fs2](https://github.com/jarxorg/fs2). ## Delegator @@ -81,6 +44,12 @@ func main() { ### No-op Closer using Delegator ```go +// NopReadCloser returns a ReadCloser with a no-op Close method wrapping the provided interface. +// This function like io.NopCloser(io.Reader). +func NopReadCloser(r io.Reader) io.ReadCloser { + return DelegateReader(r) +} + // NopReadWriteCloser returns a ReadWriteCloser with a no-op Close method wrapping the provided interface. func NopReadWriteCloser(rw io.ReadWriter) io.ReadWriteCloser { return DelegateReadWriter(rw) @@ -97,37 +66,6 @@ func NopWriteCloser(w io.Writer) io.WriteCloser { } ``` -## FSDelegator and FileDelegator - -FSDelegator implements FS, ReadDirFS, ReadFileFS, StatFS, SubFS of [io/fs](https://github.com/golang/go/tree/master/src/io/fs) package. -FSDelegator can override the FS functions that is useful for unit tests. - -```go -package main - -import ( - "errors" - "fmt" - "io/fs" - "os" - - "github.com/jarxorg/io2" -) - -func main() { - fsys := io2.DelegateFS(os.DirFS(".")) - fsys.ReadDirFunc = func(name string) ([]fs.DirEntry, error) { - return nil, errors.New("custom") - } - - var err error - _, err = fs.ReadDir(fsys, ".") - fmt.Printf("Error: %v\n", err) - - // Output: Error: custom -} -``` - ## WriteSeekBuffer WriteSeekBuffer implements io.Writer, io.Seeker and io.Closer. diff --git a/example_test.go b/example_test.go index 7a81dee..6779724 100644 --- a/example_test.go +++ b/example_test.go @@ -5,41 +5,11 @@ import ( "errors" "fmt" "io" - "io/fs" "io/ioutil" - "log" - "os" "github.com/jarxorg/io2" - "github.com/jarxorg/io2/osfs" ) -func ExampleWriteFile() { - tmpDir, err := ioutil.TempDir("", "example") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - name := "example.txt" - content := []byte(`Hello`) - - fsys := osfs.DirFS(tmpDir) - _, err = io2.WriteFile(fsys, name, content, fs.ModePerm) - if err != nil { - log.Fatal(err) - } - - wrote, err := ioutil.ReadFile(tmpDir + "/" + name) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%s\n", string(wrote)) - - // Output: Hello -} - func ExampleDelegateReader() { org := bytes.NewReader([]byte(`original`)) @@ -55,37 +25,6 @@ func ExampleDelegateReader() { // Output: Error: custom } -func ExampleDelegateFS() { - fsys := io2.DelegateFS(os.DirFS(".")) - fsys.ReadDirFunc = func(name string) ([]fs.DirEntry, error) { - return nil, errors.New("custom") - } - - var err error - _, err = fs.ReadDir(fsys, ".") - fmt.Printf("Error: %v\n", err) - - // Output: Error: custom -} - -func ExampleDelegateFile() { - fsys := io2.DelegateFS(os.DirFS(".")) - fsys.OpenFunc = func(name string) (fs.File, error) { - return &io2.FileDelegator{ - StatFunc: func() (fs.FileInfo, error) { - return nil, errors.New("custom") - }, - }, nil - } - - file, _ := fsys.Open("anyfile") - var err error - _, err = file.Stat() - fmt.Printf("Error: %v\n", err) - - // Output: Error: custom -} - func ExampleNewWriteSeekerBuffer() { o := io2.NewWriteSeekBuffer(0) o.Write([]byte(`Hello!`)) diff --git a/fs.go b/fs.go deleted file mode 100644 index b04e521..0000000 --- a/fs.go +++ /dev/null @@ -1,99 +0,0 @@ -package io2 - -import ( - "io" - "io/fs" -) - -// WriterFile is a file that provides an implementation fs.File and io.Writer. -type WriterFile interface { - fs.File - io.Writer -} - -// WriteFileFS is the interface implemented by a filesystem that provides an -// optimized implementation of MkdirAll, CreateFile, WriteFile. -type WriteFileFS interface { - fs.FS - MkdirAll(dir string, mode fs.FileMode) error - CreateFile(name string, mode fs.FileMode) (WriterFile, error) - WriteFile(name string, p []byte, mode fs.FileMode) (n int, err error) -} - -// MkdirAll creates the named directory. If the filesystem implements -// WriteFileFS calls fsys.MkdirAll otherwise returns a PathError. -func MkdirAll(fsys fs.FS, dir string, mode fs.FileMode) error { - if fsys, ok := fsys.(WriteFileFS); ok { - return fsys.MkdirAll(dir, mode) - } - return &fs.PathError{Op: "MkdirAll", Path: dir, Err: ErrNotImplemented} -} - -// CreateFile creates the named file. If the filesystem implements -// WriteFileFS calls fsys.CreateFile otherwise returns a PathError. -func CreateFile(fsys fs.FS, name string, mode fs.FileMode) (WriterFile, error) { - if fsys, ok := fsys.(WriteFileFS); ok { - return fsys.CreateFile(name, mode) - } - return nil, &fs.PathError{Op: "CreateFile", Path: name, Err: ErrNotImplemented} -} - -// WriteFile writes the specified bytes to the named file. If the filesystem implements -// WriteFileFS calls fsys.WriteFile otherwise returns a PathError. -func WriteFile(fsys fs.FS, name string, p []byte, mode fs.FileMode) (n int, err error) { - if fsys, ok := fsys.(WriteFileFS); ok { - return fsys.WriteFile(name, p, mode) - } - return 0, &fs.PathError{Op: "WriteFile", Path: name, Err: ErrNotImplemented} -} - -// RemoveFileFS is the interface implemented by a filesystem that provides an -// implementation of RemoveFile. -type RemoveFileFS interface { - fs.FS - RemoveFile(name string) error - RemoveAll(name string) error -} - -// RemoveFile removes the specified named file. If the filesystem implements -// RemoveFileFS calls fsys.RemoveFile otherwise return a PathError. -func RemoveFile(fsys fs.FS, name string) error { - if fsys, ok := fsys.(RemoveFileFS); ok { - return fsys.RemoveFile(name) - } - return &fs.PathError{Op: "RemoveFile", Path: name, Err: ErrNotImplemented} -} - -// RemoveAll removes path and any children it contains. If the filesystem -// implements RemoveFileFS calls fsys.RemoveAll otherwise return a PathError. -func RemoveAll(fsys fs.FS, path string) error { - if fsys, ok := fsys.(RemoveFileFS); ok { - return fsys.RemoveAll(path) - } - return &fs.PathError{Op: "RemoveAll", Path: path, Err: ErrNotImplemented} -} - -// CopyFS walks the specified root directory on src and copies directories and -// files to dest filesystem. -func CopyFS(dest, src fs.FS, root string) error { - return fs.WalkDir(src, root, func(path string, d fs.DirEntry, err error) error { - if err != nil || d == nil { - return err - } - if d.IsDir() { - return MkdirAll(dest, path, d.Type()) - } - srcFile, err := src.Open(path) - if err != nil { - return err - } - destFile, err := CreateFile(dest, path, d.Type()) - if err != nil { - return err - } - defer destFile.Close() - - _, err = io.Copy(destFile, srcFile) - return err - }) -} diff --git a/fs_test.go b/fs_test.go deleted file mode 100644 index 10971e9..0000000 --- a/fs_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package io2 - -import ( - "errors" - "io/fs" - "io/ioutil" - "os" - "reflect" - "testing" -) - -func TestMkdirAll(t *testing.T) { - got := "" - fsys := &FSDelegator{ - MkdirAllFunc: func(dir string, _ fs.FileMode) error { - got = dir - return nil - }, - } - - want := "path/to/dir" - err := MkdirAll(fsys, want, fs.ModePerm) - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("Error MkdirAll called with %s; want %s", got, want) - } -} - -func TestMkdirAll_ErrNotImplemented(t *testing.T) { - fsys := &OpenFSDelegator{} - - dir := "path/to/dir" - wantErr := &fs.PathError{Op: "MkdirAll", Path: dir, Err: ErrNotImplemented} - - err := MkdirAll(fsys, dir, fs.ModePerm) - if err == nil { - t.Errorf("Error MkdirAll returns no error") - } - gotErr, ok := err.(*fs.PathError) - if !ok { - t.Errorf("Error MkdirAll returns unknown error %v", err) - } - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error MkdirAll returns unknown error %v; want %v", gotErr, wantErr) - } -} - -func TestCreateFile(t *testing.T) { - want := &FileDelegator{} - called := false - fsys := &FSDelegator{ - CreateFileFunc: func(_ string, _ fs.FileMode) (WriterFile, error) { - called = true - return want, nil - }, - } - - got, err := CreateFile(fsys, "test.txt", fs.ModePerm) - if err != nil { - t.Fatal(err) - } - if !called { - t.Error("Error CreateFile is not called") - } - if !reflect.DeepEqual(got, want) { - t.Errorf("Error CreateFile returns %v; want %v", got, want) - } -} - -func TestCreateFile_ErrNotImplemented(t *testing.T) { - fsys := &OpenFSDelegator{} - - name := "test.txt" - wantErr := &fs.PathError{Op: "CreateFile", Path: name, Err: ErrNotImplemented} - - var err error - _, err = CreateFile(fsys, name, fs.ModePerm) - if err == nil { - t.Errorf("Error CreateFile returns no error") - } - gotErr, ok := err.(*fs.PathError) - if !ok { - t.Errorf("Error CreateFile returns unknown error %v", err) - } - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error CreateFile returns unknown error %v; want %v", gotErr, wantErr) - } -} - -func TestWriteFile(t *testing.T) { - want := 1 - called := false - fsys := &FSDelegator{ - WriteFileFunc: func(_ string, _ []byte, _ fs.FileMode) (int, error) { - called = true - return want, nil - }, - } - - got, err := WriteFile(fsys, "", []byte{}, fs.ModePerm) - if err != nil { - t.Fatal(err) - } - if !called { - t.Error("Error WriteFile is not called") - } - if got != want { - t.Errorf("Error WriteFile returns %d; want %d", got, want) - } -} - -func TestWriteFile_ErrNotImplemented(t *testing.T) { - fsys := &OpenFSDelegator{} - - name := "test.txt" - wantErr := &fs.PathError{Op: "WriteFile", Path: name, Err: ErrNotImplemented} - - var err error - _, err = WriteFile(fsys, name, []byte{}, fs.ModePerm) - if err == nil { - t.Errorf("Error WriteFile returns no error") - } - gotErr, ok := err.(*fs.PathError) - if !ok { - t.Errorf("Error WriteFile returns unknown error %v", err) - } - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error WriteFile returns unknown error %v; want %v", gotErr, wantErr) - } -} - -func TestRemoveFile(t *testing.T) { - called := false - fsys := &FSDelegator{ - RemoveFileFunc: func(name string) error { - called = true - return nil - }, - } - - err := RemoveFile(fsys, "") - if err != nil { - t.Fatal(err) - } - if !called { - t.Error("Error RemoveFile is not called") - } -} - -func TestRemoveAll(t *testing.T) { - called := false - fsys := &FSDelegator{ - RemoveAllFunc: func(name string) error { - called = true - return nil - }, - } - - err := RemoveAll(fsys, "") - if err != nil { - t.Fatal(err) - } - if !called { - t.Error("Error RemoveAll is not called") - } -} - -func TestRemoveFile_ErrNotImplemented(t *testing.T) { - fsys := &OpenFSDelegator{} - - name := "test.txt" - wantErr := &fs.PathError{Op: "RemoveFile", Path: name, Err: ErrNotImplemented} - - err := RemoveFile(fsys, name) - if err == nil { - t.Errorf("Error RemoveFile returns no error") - } - gotErr, ok := err.(*fs.PathError) - if !ok { - t.Errorf("Error RemoveFile returns unknown error %v", err) - } - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error RemoveFile returns unknown error %v; want %v", gotErr, wantErr) - } -} - -func TestRemoveAll_ErrNotImplemented(t *testing.T) { - fsys := &OpenFSDelegator{} - - path := "path/to/dir" - wantErr := &fs.PathError{Op: "RemoveAll", Path: path, Err: ErrNotImplemented} - - err := RemoveAll(fsys, path) - if err == nil { - t.Errorf("Error RemoveAll returns no error") - } - gotErr, ok := err.(*fs.PathError) - if !ok { - t.Errorf("Error RemoveAll returns unknown error %v", err) - } - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error RemoveAll returns unknown error %v; want %v", gotErr, wantErr) - } -} - -func TestCopyFS(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - root := "dir0" - want := map[string][]byte{} - src := os.DirFS("osfs/testdata") - err = fs.WalkDir(src, root, func(path string, d fs.DirEntry, err error) error { - if err != nil || d.IsDir() { - return err - } - p, err := fs.ReadFile(src, path) - if err != nil { - return err - } - want[path] = p - return nil - }) - if err != nil { - t.Fatal(err) - } - - got := map[string][]byte{} - dest := DelegateFS(os.DirFS(tmpDir)) - dest.CreateFileFunc = func(name string, mode fs.FileMode) (WriterFile, error) { - return &FileDelegator{ - WriteFunc: func(p []byte) (int, error) { - got[name] = p - return len(p), nil - }, - }, nil - } - - err = CopyFS(dest, src, root) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("Error CopyFS %v; want %v", got, want) - } -} - -func TestCopyFS_StatError(t *testing.T) { - wantErr := errors.New("test") - - src := DelegateFS(os.DirFS("osfs/testdata")) - src.StatFunc = func(name string) (fs.FileInfo, error) { - return nil, wantErr - } - - gotErr := CopyFS(&FSDelegator{}, src, ".") - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error CopyFS returns unknown error %+v; want %v", gotErr, wantErr) - } -} - -func TestCopyFS_OpenError(t *testing.T) { - wantErr := errors.New("test") - - src := DelegateFS(os.DirFS("osfs/testdata")) - src.OpenFunc = func(name string) (fs.File, error) { - return nil, wantErr - } - - gotErr := CopyFS(&FSDelegator{}, src, ".") - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error CopyFS returns unknown error %+v; want %v", gotErr, wantErr) - } -} - -func TestCopyFS_CreateFileError(t *testing.T) { - wantErr := errors.New("test") - - src := os.DirFS("osfs/testdata") - dest := &FSDelegator{ - CreateFileFunc: func(_ string, _ fs.FileMode) (WriterFile, error) { - return nil, wantErr - }, - } - - gotErr := CopyFS(dest, src, ".") - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error CopyFS returns unknown error %+v; want %v", gotErr, wantErr) - } -} diff --git a/fsdelegator.go b/fsdelegator.go deleted file mode 100644 index 65a3d70..0000000 --- a/fsdelegator.go +++ /dev/null @@ -1,374 +0,0 @@ -package io2 - -import ( - "io/fs" - "time" -) - -// OpenFSDelegator implements fs.FS interface. -type OpenFSDelegator struct { - OpenFunc func(name string) (fs.File, error) -} - -// Open calls OpenFunc(name). -func (d *OpenFSDelegator) Open(name string) (fs.File, error) { - if d.OpenFunc == nil { - return nil, &fs.PathError{Op: "Open", Path: name, Err: ErrNotImplemented} - } - return d.OpenFunc(name) -} - -// DelegateOpenFS returns a OpenFSDelegator delegates fsys.Open. -func DelegateOpenFS(fsys fs.FS) *OpenFSDelegator { - return &OpenFSDelegator{OpenFunc: fsys.Open} -} - -// FSDelegator implements all filesystem interfaces in io/fs and WriteFileFS. -type FSDelegator struct { - OpenFunc func(name string) (fs.File, error) - ReadDirFunc func(name string) ([]fs.DirEntry, error) - ReadFileFunc func(name string) ([]byte, error) - GlobFunc func(pattern string) ([]string, error) - StatFunc func(name string) (fs.FileInfo, error) - SubFunc func(dir string) (fs.FS, error) - MkdirAllFunc func(dir string, mode fs.FileMode) error - CreateFileFunc func(name string, mode fs.FileMode) (WriterFile, error) - WriteFileFunc func(name string, p []byte, mode fs.FileMode) (int, error) - RemoveFileFunc func(name string) error - RemoveAllFunc func(path string) error -} - -var ( - _ fs.FS = (*FSDelegator)(nil) - _ fs.GlobFS = (*FSDelegator)(nil) - _ fs.ReadDirFS = (*FSDelegator)(nil) - _ fs.ReadFileFS = (*FSDelegator)(nil) - _ fs.StatFS = (*FSDelegator)(nil) - _ fs.SubFS = (*FSDelegator)(nil) - _ WriteFileFS = (*FSDelegator)(nil) - _ RemoveFileFS = (*FSDelegator)(nil) -) - -// Open calls OpenFunc(name). -func (d *FSDelegator) Open(name string) (fs.File, error) { - if d.OpenFunc == nil { - return nil, &fs.PathError{Op: "Open", Path: name, Err: ErrNotImplemented} - } - return d.OpenFunc(name) -} - -// ReadDir calls ReadDirFunc(name). -func (d *FSDelegator) ReadDir(name string) ([]fs.DirEntry, error) { - if d.ReadDirFunc == nil { - return nil, &fs.PathError{Op: "ReadDir", Path: name, Err: ErrNotImplemented} - } - return d.ReadDirFunc(name) -} - -// ReadFile calls ReadFileFunc(name). -func (d *FSDelegator) ReadFile(name string) ([]byte, error) { - if d.ReadFileFunc == nil { - return nil, &fs.PathError{Op: "ReadFile", Path: name, Err: ErrNotImplemented} - } - return d.ReadFileFunc(name) -} - -// Glob calls GlobFunc(name). -func (d *FSDelegator) Glob(pattern string) ([]string, error) { - if d.GlobFunc == nil { - return nil, &fs.PathError{Op: "Glob", Path: pattern, Err: ErrNotImplemented} - } - return d.GlobFunc(pattern) -} - -// Stat calls StatFunc(name). -func (d *FSDelegator) Stat(name string) (fs.FileInfo, error) { - if d.StatFunc == nil { - return nil, &fs.PathError{Op: "Stat", Path: name, Err: ErrNotImplemented} - } - return d.StatFunc(name) -} - -// Sub calls SubFunc(name). -func (d *FSDelegator) Sub(name string) (fs.FS, error) { - if d.SubFunc == nil { - return nil, &fs.PathError{Op: "Sub", Path: name, Err: ErrNotImplemented} - } - return d.SubFunc(name) -} - -// MkdirAll calls MkdirAllFunc(dir). -func (d *FSDelegator) MkdirAll(dir string, mode fs.FileMode) error { - if d.MkdirAllFunc == nil { - // NOTE: return no error. - return nil - } - return d.MkdirAllFunc(dir, mode) -} - -// CreateFile calls CreateFileFunc(name). -func (d *FSDelegator) CreateFile(name string, mode fs.FileMode) (WriterFile, error) { - if d.CreateFileFunc == nil { - return nil, &fs.PathError{Op: "CreateFile", Path: name, Err: ErrNotImplemented} - } - return d.CreateFileFunc(name, mode) -} - -// WriteFile calls WriteFileFunc(name). -func (d *FSDelegator) WriteFile(name string, p []byte, mode fs.FileMode) (int, error) { - if d.WriteFileFunc == nil { - return 0, &fs.PathError{Op: "WriteFile", Path: name, Err: ErrNotImplemented} - } - return d.WriteFileFunc(name, p, mode) -} - -// RemoveFile calls RemoveFileFunc(name). -func (d *FSDelegator) RemoveFile(name string) error { - if d.RemoveFileFunc == nil { - return &fs.PathError{Op: "RemoveFile", Path: name, Err: ErrNotImplemented} - } - return d.RemoveFileFunc(name) -} - -// RemoveAll calls RemoveAllFunc(name). -func (d *FSDelegator) RemoveAll(path string) error { - if d.RemoveAllFunc == nil { - return &fs.PathError{Op: "RemoveAll", Path: path, Err: ErrNotImplemented} - } - return d.RemoveAllFunc(path) -} - -// DelegateFS returns a FSDelegator delegates the functions of the specified filesystem. -// If you want to delegate an open only filesystem like os.DirFS(dir string) use DelegateOpenFS instead. -func DelegateFS(fsys fs.FS) *FSDelegator { - d := &FSDelegator{ - OpenFunc: fsys.Open, - } - if casted, ok := fsys.(fs.ReadDirFS); ok { - d.ReadDirFunc = casted.ReadDir - } else { - d.ReadDirFunc = func(name string) ([]fs.DirEntry, error) { - return fs.ReadDir(fsys, name) - } - } - if casted, ok := fsys.(fs.ReadFileFS); ok { - d.ReadFileFunc = casted.ReadFile - } else { - d.ReadFileFunc = func(name string) ([]byte, error) { - return fs.ReadFile(fsys, name) - } - } - if casted, ok := fsys.(fs.GlobFS); ok { - d.GlobFunc = casted.Glob - } else { - d.GlobFunc = func(pattern string) ([]string, error) { - return fs.Glob(fsys, pattern) - } - } - if casted, ok := fsys.(fs.StatFS); ok { - d.StatFunc = casted.Stat - } else { - d.StatFunc = func(name string) (fs.FileInfo, error) { - return fs.Stat(fsys, name) - } - } - if casted, ok := fsys.(fs.SubFS); ok { - d.SubFunc = casted.Sub - } else { - d.SubFunc = func(dir string) (fs.FS, error) { - return fs.Sub(fsys, dir) - } - } - if casted, ok := fsys.(WriteFileFS); ok { - d.CreateFileFunc = casted.CreateFile - d.WriteFileFunc = casted.WriteFile - } - if casted, ok := fsys.(RemoveFileFS); ok { - d.RemoveFileFunc = casted.RemoveFile - d.RemoveAllFunc = casted.RemoveAll - } - return d -} - -// FileDelegator implements fs.File, fs.ReadDirFile and WriterFile interface. -type FileDelegator struct { - StatFunc func() (fs.FileInfo, error) - ReadFunc func(p []byte) (int, error) - CloseFunc func() error - ReadDirFunc func(n int) ([]fs.DirEntry, error) - WriteFunc func(p []byte) (int, error) -} - -var ( - _ fs.File = (*FileDelegator)(nil) - _ fs.ReadDirFile = (*FileDelegator)(nil) - _ WriterFile = (*FileDelegator)(nil) -) - -// Stat calls StatFunc(). -func (f *FileDelegator) Stat() (fs.FileInfo, error) { - if f.StatFunc == nil { - return nil, ErrNotImplemented - } - return f.StatFunc() -} - -// Read calls ReadFunc(p). -func (f *FileDelegator) Read(p []byte) (int, error) { - if f.ReadFunc == nil { - return 0, ErrNotImplemented - } - return f.ReadFunc(p) -} - -// Close calls CloseFunc(). -func (f *FileDelegator) Close() error { - if f.CloseFunc == nil { - // NOTE: return no error. - return nil - } - return f.CloseFunc() -} - -// ReadDir calls ReadDirFunc(n). -func (f *FileDelegator) ReadDir(n int) ([]fs.DirEntry, error) { - if f.ReadDirFunc == nil { - return nil, ErrNotImplemented - } - return f.ReadDirFunc(n) -} - -// Write calls WriteFunc(n). -func (f *FileDelegator) Write(p []byte) (int, error) { - if f.WriteFunc == nil { - return 0, ErrNotImplemented - } - return f.WriteFunc(p) -} - -// DelegateFile returns a FileDelegator delegates the functions of the specified file. -func DelegateFile(f fs.File) *FileDelegator { - d := &FileDelegator{ - StatFunc: f.Stat, - ReadFunc: f.Read, - CloseFunc: f.Close, - } - if f, ok := f.(fs.ReadDirFile); ok { - d.ReadDirFunc = f.ReadDir - } - if f, ok := f.(WriterFile); ok { - d.WriteFunc = f.Write - } - return d -} - -// DirEntryValues holds values for fs.DirEntry. -type DirEntryValues struct { - Name string - IsDir bool - Type fs.FileMode - Info fs.FileInfo -} - -// DirEntryDelegator implements fs.DirEntry. -type DirEntryDelegator struct { - Values DirEntryValues - InfoFunc func() (fs.FileInfo, error) -} - -var _ (fs.DirEntry) = (*DirEntryDelegator)(nil) - -// Name returns d.Values.Name. -func (d *DirEntryDelegator) Name() string { - return d.Values.Name -} - -// IsDir returns d.Values.IsDir. -func (d *DirEntryDelegator) IsDir() bool { - return d.Values.IsDir -} - -// Type returns d.Values.Type. -func (d *DirEntryDelegator) Type() fs.FileMode { - return d.Values.Type -} - -// Info calls d.InfoFunc if the function is set otherwise returns d.Values.Info. -func (d *DirEntryDelegator) Info() (fs.FileInfo, error) { - if d.InfoFunc != nil { - return d.InfoFunc() - } - return d.Values.Info, nil -} - -// DelegateDirEntry returns a DirEntryDelegator delegates the functions of the specified DirEntry. -func DelegateDirEntry(d fs.DirEntry) *DirEntryDelegator { - return &DirEntryDelegator{ - Values: DirEntryValues{ - Name: d.Name(), - IsDir: d.IsDir(), - Type: d.Type(), - }, - InfoFunc: d.Info, - } -} - -// FileInfoValues holds values for fs.FileInfo. -type FileInfoValues struct { - Name string - Size int64 - Mode fs.FileMode - ModTime time.Time - IsDir bool - Sys interface{} -} - -// FileInfoDelegator implements fs.FileInfo. -type FileInfoDelegator struct { - Values FileInfoValues -} - -var _ (fs.FileInfo) = (*FileInfoDelegator)(nil) - -// Name returns d.Values.Name. -func (d *FileInfoDelegator) Name() string { - return d.Values.Name -} - -// Size returns d.Values.Size. -func (d *FileInfoDelegator) Size() int64 { - return d.Values.Size -} - -// Mode returns d.Values.Mode. -func (d *FileInfoDelegator) Mode() fs.FileMode { - return d.Values.Mode -} - -// ModTime returns d.Values.ModTime. -func (d *FileInfoDelegator) ModTime() time.Time { - return d.Values.ModTime -} - -func (d *FileInfoDelegator) IsDir() bool { - return d.Values.IsDir -} - -// Sys returns d.Values.Sys. -func (d *FileInfoDelegator) Sys() interface{} { - return d.Values.Sys -} - -// DelegateFileInfo returns a FileInfoDelegator delegates the functions of the specified FileInfo. -func DelegateFileInfo(info fs.FileInfo) *FileInfoDelegator { - return &FileInfoDelegator{ - Values: FileInfoValues{ - Name: info.Name(), - Size: info.Size(), - Mode: info.Mode(), - ModTime: info.ModTime(), - IsDir: info.IsDir(), - Sys: info.Sys(), - }, - } -} diff --git a/fsdelegator_test.go b/fsdelegator_test.go deleted file mode 100644 index 44068df..0000000 --- a/fsdelegator_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package io2 - -import ( - "errors" - "io/fs" - "os" - "reflect" - "testing" - "testing/fstest" -) - -func TestOpenFSDelegator_TestFS(t *testing.T) { - d := DelegateOpenFS(os.DirFS("osfs/testdata")) - if err := fstest.TestFS(d, "dir0/file01.txt"); err != nil { - t.Errorf(`Error testing/fstest: %+v`, err) - } -} - -func TestOpenFSDelegator_ErrNotImplemented(t *testing.T) { - d := &OpenFSDelegator{} - var err error - _, err = d.Open("") - if !errors.Is(err, ErrNotImplemented) { - t.Errorf(`Error unknown: %v`, err) - } -} - -func TestFSDelegator_TestFS(t *testing.T) { - d := DelegateFS(os.DirFS("osfs/testdata")) - if err := fstest.TestFS(d, "dir0/file01.txt"); err != nil { - t.Errorf(`Error testing/fstest: %+v`, err) - } -} - -func testFSDelegatorErrors(t *testing.T, d *FSDelegator, wantErr error) { - var err error - if _, err = d.Open(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.ReadDir(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.ReadFile(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.Glob(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.Stat(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.Sub(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if err = d.MkdirAll("", fs.ModePerm); err != nil { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.CreateFile("", fs.ModePerm); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.WriteFile("", []byte{}, fs.ModePerm); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if err = d.RemoveFile(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if err = d.RemoveAll(""); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } -} - -func TestFSDelegator_ErrNotImplemented(t *testing.T) { - testFSDelegatorErrors(t, &FSDelegator{}, ErrNotImplemented) -} - -func TestFSDelegator(t *testing.T) { - wantErr := errors.New("test") - - testFSDelegatorErrors(t, &FSDelegator{ - OpenFunc: func(_ string) (fs.File, error) { - return nil, wantErr - }, - ReadDirFunc: func(_ string) ([]fs.DirEntry, error) { - return nil, wantErr - }, - ReadFileFunc: func(_ string) ([]byte, error) { - return nil, wantErr - }, - GlobFunc: func(_ string) ([]string, error) { - return nil, wantErr - }, - StatFunc: func(_ string) (fs.FileInfo, error) { - return nil, wantErr - }, - SubFunc: func(_ string) (fs.FS, error) { - return nil, wantErr - }, - MkdirAllFunc: func(_ string, _ fs.FileMode) error { - return nil - }, - CreateFileFunc: func(_ string, _ fs.FileMode) (WriterFile, error) { - return nil, wantErr - }, - WriteFileFunc: func(_ string, _ []byte, _ fs.FileMode) (int, error) { - return 0, wantErr - }, - RemoveFileFunc: func(_ string) error { - return wantErr - }, - RemoveAllFunc: func(_ string) error { - return wantErr - }, - }, wantErr) -} - -func TestDelegateFS(t *testing.T) { - DelegateFS(&FSDelegator{}) -} - -func TestDelegateFS_ReadDir(t *testing.T) { - fsys := os.DirFS("osfs/testdata") - path := "dir0" - want, err := fs.ReadDir(fsys, path) - if err != nil { - t.Fatal(err) - } - d := DelegateFS(fsys) - got, err := d.ReadDir(path) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error ReadDir returns %v; want %v`, got, want) - } -} - -func TestDelegateFS_ReadFile(t *testing.T) { - fsys := os.DirFS("osfs/testdata") - path := "dir0/file01.txt" - want, err := fs.ReadFile(fsys, path) - if err != nil { - t.Fatal(err) - } - d := DelegateFS(fsys) - got, err := d.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error ReadFile returns %v; want %v`, got, want) - } -} - -func TestDelegateFS_Glob(t *testing.T) { - fsys := os.DirFS("osfs/testdata") - pattern := "dir0/*.txt" - want, err := fs.Glob(fsys, pattern) - if err != nil { - t.Fatal(err) - } - d := DelegateFS(fsys) - got, err := d.Glob(pattern) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error ReadFile returns %v; want %v`, got, want) - } -} - -func TestDelegateFS_Stat(t *testing.T) { - fsys := os.DirFS("osfs/testdata") - path := "dir0/file01.txt" - want, err := fs.Stat(fsys, path) - if err != nil { - t.Fatal(err) - } - d := DelegateFS(fsys) - got, err := d.Stat(path) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error Stat returns %v; want %v`, got, want) - } -} - -func TestDelegateFS_Sub(t *testing.T) { - fsys := os.DirFS("osfs/testdata") - path := "dir0" - want, err := fs.Sub(fsys, path) - if err != nil { - t.Fatal(err) - } - d := DelegateFS(fsys) - got, err := d.Sub(path) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error Sub returns %v; want %v`, got, want) - } -} - -func TestDelegateFile(t *testing.T) { - DelegateFile(&FileDelegator{}) -} - -func testFileDelegatorErrors(t *testing.T, d *FileDelegator, wantErr error) { - var err error - if _, err = d.Stat(); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.Read([]byte{}); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if err = d.Close(); err != nil { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.ReadDir(-1); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } - if _, err = d.Write([]byte{}); !errors.Is(err, wantErr) { - t.Errorf(`Error unknown: %v`, err) - } -} - -func TestFileDelegator_ErrNotImplemented(t *testing.T) { - testFileDelegatorErrors(t, &FileDelegator{}, ErrNotImplemented) -} - -func TestFileDelegator(t *testing.T) { - wantErr := errors.New("test") - - testFileDelegatorErrors(t, &FileDelegator{ - StatFunc: func() (fs.FileInfo, error) { - return nil, wantErr - }, - ReadFunc: func(p []byte) (int, error) { - return 0, wantErr - }, - CloseFunc: func() error { - return nil - }, - ReadDirFunc: func(n int) ([]fs.DirEntry, error) { - return nil, wantErr - }, - WriteFunc: func(p []byte) (int, error) { - return 0, wantErr - }, - }, wantErr) -} - -func TestDirEntryDelegator(t *testing.T) { - fsys := os.DirFS("osfs/testdata") - ds, err := fs.ReadDir(fsys, ".") - if err != nil { - t.Fatal(err) - } - if len(ds) == 0 { - t.Fatal(`Fatal ReadDir returns empty.`) - } - - want := ds[0] - got := DelegateDirEntry(want) - if got.Name() != want.Name() { - t.Errorf(`Error Name got %s; want %s`, got.Name(), want.Name()) - } - if got.IsDir() != want.IsDir() { - t.Errorf(`Error IsDir got %v; want %v`, got.IsDir(), want.IsDir()) - } - if got.Type() != want.Type() { - t.Errorf(`Error Type got %v; want %v`, got.Type(), want.Type()) - } - - wantInfo, err := want.Info() - if err != nil { - t.Fatal(err) - } - gotInfo, err := got.Info() - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(gotInfo, wantInfo) { - t.Errorf(`Error Info returns %v; want %v`, gotInfo, wantInfo) - } - - got.InfoFunc = nil - gotInfo, err = got.Info() - if err != nil { - t.Fatal(err) - } - if gotInfo != nil { - t.Errorf(`Error info returns %v; want nil`, gotInfo) - } -} - -func TestInfoInfoDelegator(t *testing.T) { - fsys := os.DirFS("osfs/testdata") - want, err := fs.Stat(fsys, "dir0/file01.txt") - if err != nil { - t.Fatal(err) - } - - got := DelegateFileInfo(want) - if got.Name() != want.Name() { - t.Errorf(`Error Name got %s; want %s`, got.Name(), want.Name()) - } - if got.Size() != want.Size() { - t.Errorf(`Error Size got %d; want %d`, got.Size(), want.Size()) - } - if got.Mode() != want.Mode() { - t.Errorf(`Error Mode got %v; want %v`, got.Mode(), want.Mode()) - } - if got.ModTime() != want.ModTime() { - t.Errorf(`Error ModTime got %v; want %v`, got.ModTime(), want.ModTime()) - } - if got.IsDir() != want.IsDir() { - t.Errorf(`Error IsDir got %v; want %v`, got.IsDir(), want.IsDir()) - } - if got.Sys() != want.Sys() { - t.Errorf(`Error Sys got %v; want %v`, got.Sys(), want.Sys()) - } -} diff --git a/memfs/example_test.go b/memfs/example_test.go deleted file mode 100644 index 8c180d1..0000000 --- a/memfs/example_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package memfs_test - -import ( - "fmt" - "io/fs" - "log" - - "github.com/jarxorg/io2" - "github.com/jarxorg/io2/memfs" -) - -func ExampleNew() { - name := "path/to/example.txt" - content := []byte(`Hello`) - - fsys := memfs.New() - var err error - _, err = io2.WriteFile(fsys, name, content, fs.ModePerm) - if err != nil { - log.Fatal(err) - } - - wrote, err := fs.ReadFile(fsys, name) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%s\n", string(wrote)) - - // Output: Hello -} diff --git a/memfs/memfs.go b/memfs/memfs.go deleted file mode 100644 index d69d0c6..0000000 --- a/memfs/memfs.go +++ /dev/null @@ -1,354 +0,0 @@ -// Package memfs provides an in-memory filesystem. -package memfs - -import ( - "bytes" - "io" - "io/fs" - "path/filepath" - "strings" - "sync" - "syscall" - - "github.com/jarxorg/io2" -) - -// MemFS represents an in-memory filesystem. -// MemFS keeps fs.FileMode but that permission is not checked. -type MemFS struct { - mutex sync.Mutex - dir string - store *store -} - -var ( - _ fs.FS = (*MemFS)(nil) - _ fs.GlobFS = (*MemFS)(nil) - _ fs.ReadDirFS = (*MemFS)(nil) - _ fs.ReadFileFS = (*MemFS)(nil) - _ fs.StatFS = (*MemFS)(nil) - _ fs.SubFS = (*MemFS)(nil) - _ io2.WriteFileFS = (*MemFS)(nil) - _ io2.RemoveFileFS = (*MemFS)(nil) -) - -// New returns a new MemFS. -func New() *MemFS { - return &MemFS{ - dir: "/", - store: newStore(), - } -} - -func (fsys *MemFS) key(name string) string { - return filepath.Clean(filepath.Join(fsys.dir, name)) -} - -func (fsys *MemFS) open(name string) (*value, error) { - if !fs.ValidPath(name) { - return nil, &fs.PathError{Op: "Open", Path: name, Err: fs.ErrInvalid} - } - v := fsys.store.get(fsys.key(name)) - if v == nil { - return nil, &fs.PathError{Op: "Open", Path: name, Err: fs.ErrNotExist} - } - return v, nil -} - -func (fsys *MemFS) mkdirAll(dir string, mode fs.FileMode) error { - if !fs.ValidPath(dir) { - return &fs.PathError{Op: "MkdirAll", Path: dir, Err: fs.ErrInvalid} - } - keys := strings.Split(fsys.key(dir), "/") - for i, k := range keys { - key := fsys.key(filepath.Join(keys[0 : i+1]...)) - if v := fsys.store.get(key); v != nil { - if !v.isDir { - return &fs.PathError{Op: "MkdirAll", Path: dir, Err: fs.ErrInvalid} - } - continue - } - if k == "" { - k = "." - } - v := &value{name: k, mode: mode | fs.ModeDir, isDir: true} - fsys.store.put(key, v) - } - return nil -} - -func (fsys *MemFS) create(name string, mode fs.FileMode) (*value, error) { - if !fs.ValidPath(name) { - return nil, &fs.PathError{Op: "Create", Path: name, Err: fs.ErrInvalid} - } - err := fsys.mkdirAll(filepath.Dir(name), mode) - if err != nil { - return nil, err - } - key := fsys.key(name) - v := fsys.store.get(key) - if v == nil { - v = &value{name: key, mode: mode} - fsys.store.put(key, v) - } else if v.isDir { - return nil, &fs.PathError{Op: "Create", Path: name, Err: fs.ErrInvalid} - } - return v, nil -} - -// Open opens the named file. -func (fsys *MemFS) Open(name string) (fs.File, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - v, err := fsys.open(name) - if err != nil { - return nil, err - } - - f := &MemFile{ - fsys: fsys, - name: name, - mode: v.mode, - } - if !v.isDir { - f.buf = bytes.NewBuffer(v.data) - } - return f, nil -} - -var filepathRel = func(basepath, targpath string) (string, error) { - return filepath.Rel(basepath, targpath) -} - -// Glob returns the names of all files matching pattern, providing an implementation -// of the top-level Glob function. -func (fsys *MemFS) Glob(pattern string) ([]string, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - keys, err := fsys.store.prefixGlobKeys(fsys.dir, pattern) - if err != nil { - return nil, err - } - - var names []string - for _, key := range keys { - name, err := filepathRel(fsys.dir, key) - if err != nil { - return nil, err - } - names = append(names, name) - } - return names, nil -} - -// ReadDir reads the named directory and returns a list of directory entries sorted -// by filename. -func (fsys *MemFS) ReadDir(dir string) ([]fs.DirEntry, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - v, err := fsys.open(dir) - if err != nil { - return nil, err - } - if !v.isDir { - return nil, &fs.PathError{Op: "ReadDir", Path: dir, Err: syscall.ENOTDIR} - } - - prefix := fsys.key(dir) - keys := fsys.store.prefixKeys(prefix) - var dirEntries []fs.DirEntry - for _, key := range keys { - dirEntries = append(dirEntries, fsys.store.get(key)) - } - return dirEntries, nil -} - -// ReadFile reads the named file and returns its contents. -func (fsys *MemFS) ReadFile(name string) ([]byte, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - v, err := fsys.open(name) - if err != nil { - return nil, err - } - if v.isDir { - return nil, &fs.PathError{Op: "ReadFile", Path: name, Err: fs.ErrInvalid} - } - return v.data, nil -} - -// Stat returns a FileInfo describing the file. If there is an error, it should be -// of type *PathError. -func (fsys *MemFS) Stat(name string) (fs.FileInfo, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - return fsys.open(name) -} - -// Sub returns an FS corresponding to the subtree rooted at dir. -func (fsys *MemFS) Sub(dir string) (fs.FS, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - if !fs.ValidPath(dir) { - return nil, &fs.PathError{Op: "Sub", Path: dir, Err: fs.ErrInvalid} - } - info, err := fsys.open(dir) - if err != nil { - return nil, err - } - if !info.isDir { - return nil, &fs.PathError{Op: "Sub", Path: dir, Err: fs.ErrInvalid} - } - return &MemFS{ - dir: filepath.Join(fsys.dir, dir), - store: fsys.store, - }, nil -} - -// MkdirAll creates the named directory. -func (fsys *MemFS) MkdirAll(dir string, mode fs.FileMode) error { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - return fsys.mkdirAll(dir, mode) -} - -// CreateFile creates the named file. -func (fsys *MemFS) CreateFile(name string, mode fs.FileMode) (io2.WriterFile, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - v, err := fsys.create(name, mode) - if err != nil { - return nil, err - } - return &MemFile{ - fsys: fsys, - name: name, - buf: bytes.NewBuffer(v.data), - mode: mode, - }, nil -} - -// WriteFile writes the specified bytes to the named file. -func (fsys *MemFS) WriteFile(name string, p []byte, mode fs.FileMode) (int, error) { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - v, err := fsys.create(name, mode) - if err != nil { - return 0, err - } - v.data = p[:] - return len(p), nil -} - -// RemoveFile removes the specified named file. -func (fsys *MemFS) RemoveFile(name string) error { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - if !fs.ValidPath(name) { - return &fs.PathError{Op: "RemoveFile", Path: name, Err: fs.ErrInvalid} - } - - fsys.store.remove(fsys.key(name)) - return nil -} - -// RemoveAll removes path and any children it contains. -func (fsys *MemFS) RemoveAll(path string) error { - fsys.mutex.Lock() - defer fsys.mutex.Unlock() - - if !fs.ValidPath(path) { - return &fs.PathError{Op: "RemoveAll", Path: path, Err: fs.ErrInvalid} - } - - fsys.store.removeAll(fsys.key(path)) - return nil -} - -// MemFile represents an in-memory file. -// MemFile implements fs.File, fs.ReadDirFile and io2.WriterFile. -type MemFile struct { - fsys *MemFS - name string - buf *bytes.Buffer - mode fs.FileMode - dirRead bool - dirEntries []fs.DirEntry - dirIndex int - wrote bool -} - -var ( - _ fs.File = (*MemFile)(nil) - _ fs.ReadDirFile = (*MemFile)(nil) - _ io2.WriterFile = (*MemFile)(nil) -) - -// Read reads bytes from this file. -func (f *MemFile) Read(p []byte) (int, error) { - if f.buf == nil { - return 0, &fs.PathError{Op: "Read", Path: f.name, Err: syscall.EISDIR} - } - return f.buf.Read(p) -} - -// Stat returns the fs.FileInfo of this file. -func (f *MemFile) Stat() (fs.FileInfo, error) { - return f.fsys.Stat(f.name) -} - -// Close closes streams. -func (f *MemFile) Close() error { - if f.wrote { - var err error - _, err = f.fsys.WriteFile(f.name, f.buf.Bytes(), f.mode) - return err - } - f.dirEntries = nil - return nil -} - -// ReadDir reads sub directories. -func (f *MemFile) ReadDir(n int) ([]fs.DirEntry, error) { - if !f.dirRead { - f.dirRead = true - var err error - f.dirEntries, err = f.fsys.ReadDir(f.name) - if err != nil { - return nil, err - } - } - max := len(f.dirEntries) - if f.dirIndex >= max { - if n <= 0 { - return nil, nil - } - return nil, io.EOF - } - if n <= 0 { - n = max - f.dirIndex - } - end := f.dirIndex + n - if end > max { - end = max - } - defer func() { f.dirIndex = end }() - - return f.dirEntries[f.dirIndex:end], nil -} - -// Write writes the specified bytes to this file. -func (f *MemFile) Write(p []byte) (int, error) { - f.wrote = true - return f.buf.Write(p) -} diff --git a/memfs/memfs_test.go b/memfs/memfs_test.go deleted file mode 100644 index bd75107..0000000 --- a/memfs/memfs_test.go +++ /dev/null @@ -1,515 +0,0 @@ -package memfs - -import ( - "errors" - "io" - "io/fs" - "os" - "reflect" - "strings" - "testing" - "testing/fstest" - - "github.com/jarxorg/io2" -) - -func newMemFSTest(t *testing.T) *MemFS { - fsys := New() - err := io2.CopyFS(fsys, os.DirFS("../osfs/testdata"), ".") - if err != nil { - t.Fatal(err) - } - return fsys -} - -func TestFS(t *testing.T) { - fsys := newMemFSTest(t) - - if err := fstest.TestFS(fsys, "dir0"); err != nil { - t.Errorf(`Error testing/fstest: %+v`, err) - } - if err := fstest.TestFS(fsys, "dir0/file01.txt"); err != nil { - t.Errorf(`Error testing/fstest: %+v`, err) - } -} - -func TestCreateFile(t *testing.T) { - testCases := []struct { - name string - errStr string - }{ - { - name: "file.txt", - }, { - name: "newDir/file.txt", - }, { - name: "newDir", - errStr: "Create newDir: invalid argument", - }, { - name: "newDir/file.txt/invalid", - errStr: "MkdirAll newDir/file.txt: invalid argument", - }, { - name: "../invalid", - errStr: "Create ../invalid: invalid argument", - }, { - name: "dir0/file01.txt", - }, - } - - fsys := newMemFSTest(t) - for _, tc := range testCases { - _, err := fsys.CreateFile(tc.name, fs.ModePerm) - errStr := "" - if err != nil { - errStr = err.Error() - } - if errStr != tc.errStr { - t.Errorf(`Error Create("%s") error got "%s"; want "%s"`, tc.name, errStr, tc.errStr) - } - if err != nil { - continue - } - info, err := fsys.Stat(tc.name) - if err != nil { - t.Fatal(err) - } - if info.IsDir() { - t.Errorf(`Error %s IsDir() returns true; want false`, tc.name) - } - } -} - -func TestMkdirAll(t *testing.T) { - testCases := []struct { - dir string - errStr string - }{ - { - dir: "test0", - }, { - dir: "test0/test1", - }, { - dir: "test2/test3", - }, { - dir: "../invalid", - errStr: "MkdirAll ../invalid: invalid argument", - }, { - dir: "dir0/file01.txt", - errStr: "MkdirAll dir0/file01.txt: invalid argument", - }, - } - - fsys := newMemFSTest(t) - for _, tc := range testCases { - err := fsys.MkdirAll(tc.dir, fs.ModePerm) - errStr := "" - if err != nil { - errStr = err.Error() - } - if errStr != tc.errStr { - t.Errorf(`Error MkdirAll("%s") error got "%s"; want "%s"`, tc.dir, errStr, tc.errStr) - } - if err != nil { - continue - } - info, err := fsys.Stat(tc.dir) - if err != nil { - t.Fatal(err) - } - if !info.IsDir() { - t.Errorf(`Error %s IsDir() returns false; want true`, tc.dir) - } - } -} - -func TestGlob(t *testing.T) { - testCases := []struct { - want []string - pattern string - errStr string - }{ - { - want: []string{ - "dir0/file01.txt", - }, - pattern: "*/*1.txt", - }, { - want: []string{ - "dir0/file01.txt", - "dir0/file02.txt", - }, - pattern: "dir0/*.txt", - }, { - pattern: "no-match", - }, { - pattern: "[[", - errStr: "syntax error in pattern", - }, - } - - fsys := newMemFSTest(t) - for _, tc := range testCases { - got, err := fsys.Glob(tc.pattern) - errStr := "" - if err != nil { - errStr = err.Error() - } - if errStr != tc.errStr { - t.Errorf(`Error Glob("%s") error got "%s"; want "%s"`, tc.pattern, errStr, tc.errStr) - } - if !reflect.DeepEqual(got, tc.want) { - t.Errorf(`Error Glob("%s") got %v; want %v`, tc.pattern, got, tc.want) - } - } -} - -func TestGlob_filepathRelError(t *testing.T) { - orgFilepathRel := filepathRel - defer func() { filepathRel = orgFilepathRel }() - - wantErr := errors.New("test") - filepathRel = func(basepath, targpath string) (string, error) { - return "", wantErr - } - - fsys := newMemFSTest(t) - var gotErr error - _, gotErr = fsys.Glob("*") - if gotErr != wantErr { - t.Errorf(`Error Glob error got %v; want %v`, gotErr, wantErr) - } -} - -func TestReadDir(t *testing.T) { - testCases := []struct { - want []string - dir string - errStr string - }{ - { - want: []string{ - "dir0", - }, - dir: ".", - }, { - want: []string{ - "file01.txt", - "file02.txt", - }, - dir: "dir0", - }, { - dir: "not-found", - errStr: "Open not-found: file does not exist", - }, { - dir: "dir0/file01.txt", - errStr: "ReadDir dir0/file01.txt: not a directory", - }, { - dir: "../invalid", - errStr: "Open ../invalid: invalid argument", - }, - } - - fsys := newMemFSTest(t) - for _, tc := range testCases { - entries, err := fsys.ReadDir(tc.dir) - errStr := "" - if err != nil { - errStr = err.Error() - } - if errStr != tc.errStr { - t.Errorf(`Error ReadDir("%s") error got "%s"; want "%s"`, tc.dir, errStr, tc.errStr) - } - var got []string - for _, entry := range entries { - got = append(got, entry.Name()) - } - if !reflect.DeepEqual(got, tc.want) { - t.Errorf(`Error ReadDir("%s") got %v; want %v`, tc.dir, got, tc.want) - } - } -} - -func TestReadFile(t *testing.T) { - testCases := []struct { - want []byte - name string - errStr string - }{ - { - want: []byte("content01\n"), - name: "dir0/file01.txt", - }, { - name: "not-found", - errStr: "Open not-found: file does not exist", - }, { - name: "dir0", - errStr: "ReadFile dir0: invalid argument", - }, { - name: "../invalid.txt", - errStr: "Open ../invalid.txt: invalid argument", - }, - } - - fsys := newMemFSTest(t) - for _, tc := range testCases { - got, err := fsys.ReadFile(tc.name) - errStr := "" - if err != nil { - errStr = err.Error() - } - if errStr != tc.errStr { - t.Errorf(`Error ReadFile("%s") error got "%s"; want "%s"`, tc.name, errStr, tc.errStr) - } - if !reflect.DeepEqual(got, tc.want) { - t.Errorf(`Error ReadFile("%s") got "%s"; want "%s"`, tc.name, got, tc.want) - } - } -} - -func TestSub(t *testing.T) { - fsys := newMemFSTest(t) - dir0, err := fsys.Sub("dir0") - if err != nil { - t.Fatal(err) - } - memfsDir0 := dir0.(*MemFS) - - // NOTE: Write to sub filesystem. - name := "test.txt" - want := []byte(`test`) - _, err = memfsDir0.WriteFile(name, want, fs.ModePerm) - if err != nil { - t.Fatal(err) - } - - // NOTE: Read from parent filesystem. - got, err := fsys.ReadFile("dir0/" + name) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error ReadFile("%s") got "%s"; want "%s"`, name, got, want) - } -} - -func TestSub_Errors(t *testing.T) { - testCases := []struct { - dir string - errStr string - }{ - { - dir: "../invalid", - errStr: "Sub ../invalid: invalid argument", - }, { - dir: "not-found", - errStr: "Open not-found: file does not exist", - }, { - dir: "dir0/file01.txt", - errStr: "Sub dir0/file01.txt: invalid argument", - }, - } - - fsys := newMemFSTest(t) - for _, tc := range testCases { - var err error - _, err = fsys.Sub(tc.dir) - if err == nil { - t.Fatalf(`Fatal Sub("%s") return no error`, tc.dir) - } - if err.Error() != tc.errStr { - t.Errorf(`Error Sub("%s") error got "%v"; want "%s"`, tc.dir, err, tc.errStr) - } - } -} - -func TestWriteFile(t *testing.T) { - data := []byte(`testdata`) - testCases := []struct { - name string - errStr string - }{ - { - name: "new.txt", - }, { - name: "dir0/file01.txt", - }, { - name: "dir0", - errStr: "Create dir0: invalid argument", - }, { - name: "../invalid.txt", - errStr: "Create ../invalid.txt: invalid argument", - }, - } - - fsys := newMemFSTest(t) - for _, tc := range testCases { - n, err := fsys.WriteFile(tc.name, data, fs.ModePerm) - errStr := "" - if err != nil { - errStr = err.Error() - } - if errStr != tc.errStr { - t.Errorf(`Error WriteFile("%s") error got "%s"; want "%s"`, tc.name, errStr, tc.errStr) - } - if errStr == "" && n != len(data) { - t.Errorf(`Error WriteFile("%s") returns %d; want %d`, tc.name, n, len(data)) - } - } -} - -func TestRemoveFile(t *testing.T) { - fsys := newMemFSTest(t) - name := "dir0/file01.txt" - - // NOTE: Check exists. - var err error - _, err = fsys.Stat(name) - if err != nil { - t.Fatal(err) - } - - err = fsys.RemoveFile(name) - if err != nil { - t.Fatal(err) - } - - info, err := fsys.Stat(name) - if !errors.Is(err, fs.ErrNotExist) { - t.Errorf(`Error RemoveFile("%s") after Stat returns %v`, name, info) - } -} - -func TestRemoveFile_Errors(t *testing.T) { - fsys := newMemFSTest(t) - name := "../invalid" - - want := &fs.PathError{Op: "RemoveFile", Path: name, Err: fs.ErrInvalid} - got := fsys.RemoveFile(name) - - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error RemoveFile("%s") returns %v; want %v`, name, got, want) - } -} - -func TestRemoveAll(t *testing.T) { - fsys := newMemFSTest(t) - dir := "dir0" - - var want []string - for _, k := range fsys.store.keys { - if !strings.HasPrefix(k, "/"+dir) { - want = append(want, k) - } - } - - err := fsys.RemoveAll("dir0") - if err != nil { - t.Fatal(err) - } - - got := fsys.store.keys[:] - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error RemoveAll("%s") after keys %v; want %v`, dir, got, want) - } -} - -func TestRemoveAll_Errors(t *testing.T) { - fsys := newMemFSTest(t) - name := "../invalid" - - want := &fs.PathError{Op: "RemoveAll", Path: name, Err: fs.ErrInvalid} - got := fsys.RemoveAll(name) - - if !reflect.DeepEqual(got, want) { - t.Errorf(`Error RemoveAll("%s") returns %v; want %v`, name, got, want) - } -} - -func TestMemFile_Read_Errors(t *testing.T) { - fsys := newMemFSTest(t) - name := "dir0" - - f, err := fsys.Open(name) - if err != nil { - t.Fatal(err) - } - - memf, ok := f.(*MemFile) - if !ok { - t.Fatalf(`Fatal not MemFile: %#v`, f) - } - - _, err = memf.Read([]byte{}) - if err == nil { - t.Fatalf(`Fatal Read(1) returns no error`) - } -} - -func TestMemFile_ReadDir(t *testing.T) { - fsys := newMemFSTest(t) - dir := "dir0" - - f, err := fsys.Open(dir) - if err != nil { - t.Fatal(err) - } - - memf, ok := f.(*MemFile) - if !ok { - t.Fatalf(`Fatal not MemFile: %#v`, f) - } - - testCases := []struct { - name string - err error - }{ - { - name: "file01.txt", - }, { - name: "file02.txt", - }, { - err: io.EOF, - }, - } - - for _, tc := range testCases { - entries, err := memf.ReadDir(1) - if tc.err != nil { - if !errors.Is(err, tc.err) { - t.Errorf(`Error ReadDir(1) error %v; want %v`, err, tc.err) - } - continue - } - if err != nil { - t.Fatal(err) - } - if len(entries) != 1 { - t.Errorf(`Error ReadDir(1) returns %d entries; want 1`, len(entries)) - } - if entries[0].Name() != tc.name { - t.Errorf(`Error ReadDir(1) returns unknown entries %v`, entries) - } - } -} - -func TestMemFile_ReadDir_Errors(t *testing.T) { - fsys := newMemFSTest(t) - dir := "dir0" - - f, err := fsys.Open(dir) - if err != nil { - t.Fatal(err) - } - - memf, ok := f.(*MemFile) - if !ok { - t.Fatalf(`Fatal not MemFile: %#v`, f) - } - - memf.name = "../invalid" - _, err = memf.ReadDir(1) - if err == nil { - t.Fatalf(`Fatal ReadDir(1) returns no error`) - } -} diff --git a/memfs/store.go b/memfs/store.go deleted file mode 100644 index f786650..0000000 --- a/memfs/store.go +++ /dev/null @@ -1,176 +0,0 @@ -package memfs - -import ( - "io/fs" - "path" - "path/filepath" - "sort" - "strings" - "time" -) - -// Value works as fs.DirEntry or fs.FileInfo. -type value struct { - name string - data []byte - mode fs.FileMode - modTime time.Time - isDir bool -} - -var ( - _ fs.DirEntry = (*value)(nil) - _ fs.FileInfo = (*value)(nil) -) - -func (v *value) Name() string { - return filepath.Base(v.name) -} - -func (v *value) Size() int64 { - if v.isDir { - return 0 - } - return int64(len(v.data)) -} - -func (v *value) Mode() fs.FileMode { - return v.mode -} - -func (v *value) ModTime() time.Time { - return v.modTime -} - -func (v *value) IsDir() bool { - return v.isDir -} - -func (v *value) Sys() interface{} { - return nil -} - -func (v *value) Type() fs.FileMode { - return v.mode & fs.ModeType -} - -func (v *value) Info() (fs.FileInfo, error) { - return v, nil -} - -// Store represents an in-memory key value store. -// store.keys is always sorted. -// All functions of the store are not thread safety. -type store struct { - keys []string - values map[string]*value -} - -func newStore() *store { - return &store{ - values: map[string]*value{}, - } -} - -func (s *store) get(k string) *value { - return s.values[k] -} - -func (s *store) put(k string, v *value) *value { - if _, ok := s.values[k]; !ok { - s.keys = append(s.keys, k) - sort.Strings(s.keys) - } - - s.values[k] = v - return v -} - -func (s *store) remove(key string) *value { - i := s.keyIndex(key) - if i == -1 { - return nil - } - v := s.values[key] - s.keys = append(s.keys[0:i], s.keys[i+1:]...) - delete(s.values, key) - return v -} - -func (s *store) removeAll(prefix string) { - from := s.keyIndex(prefix) - if from == -1 { - return - } - - max := len(s.keys) - to := -1 - for i := from; i < max; i++ { - key := s.keys[i] - if !strings.HasPrefix(key, prefix) { - break - } - delete(s.values, key) - to = i - } - s.keys = append(s.keys[0:from], s.keys[to+1:]...) -} - -func (s *store) keyIndex(key string) int { - i := sort.SearchStrings(s.keys, key) - if i < len(s.keys) && s.keys[i] == key { - return i - } - return -1 -} - -func (s *store) prefixKeys(prefix string) []string { - i := s.keyIndex(prefix) - if i == -1 { - return nil - } - if !strings.HasSuffix(prefix, "/") { - prefix = prefix + "/" - } - - var keys []string - max := len(s.keys) - for i++; i < max; i++ { - key := s.keys[i] - if !strings.HasPrefix(key, prefix) { - break - } - if strings.Contains(key[len(prefix):], "/") { - continue - } - keys = append(keys, key) - } - return keys -} - -func (s *store) prefixGlobKeys(prefix, pattern string) ([]string, error) { - i := s.keyIndex(prefix) - if i == -1 { - return nil, nil - } - if !strings.HasSuffix(prefix, "/") { - prefix = prefix + "/" - } - - var keys []string - max := len(s.keys) - for i++; i < max; i++ { - key := s.keys[i] - if !strings.HasPrefix(key, prefix) { - break - } - ok, err := path.Match(pattern, key[len(prefix):]) - if err != nil { - return nil, err - } - if ok { - keys = append(keys, key) - } - } - return keys, nil -} diff --git a/memfs/store_test.go b/memfs/store_test.go deleted file mode 100644 index 57e8632..0000000 --- a/memfs/store_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package memfs - -import ( - "io/fs" - "reflect" - "sort" - "strings" - "testing" - "time" -) - -func TestValue(t *testing.T) { - v := &value{ - name: "path/to/name", - data: []byte(`content`), - mode: fs.ModePerm, - modTime: time.Now(), - } - if name := v.Name(); name != "name" { - t.Errorf(`Name returns %s; want name`, name) - } - if size := v.Size(); size != int64(len(v.data)) { - t.Errorf(`Size returns %d; want %d`, size, len(v.data)) - } - v.isDir = true - if size := v.Size(); size != 0 { - t.Errorf(`dir Size returns %d; want 0`, size) - } - if mode := v.Mode(); mode != v.mode { - t.Errorf(`Mode returns %v; want %v`, mode, v.mode) - } - if modTime := v.ModTime(); modTime != v.modTime { - t.Errorf(`ModTime returns %v; want %v`, modTime, v.modTime) - } - if isDir := v.IsDir(); isDir != v.isDir { - t.Errorf(`IsDir returns %v; want %v`, isDir, v.isDir) - } - if sys := v.Sys(); sys != nil { - t.Errorf(`Sys returns %v; want nil`, sys) - } - if typ := v.Type(); typ != v.mode&fs.ModeType { - t.Errorf(`Type returns %v; want %v`, typ, v.mode) - } - info, err := v.Info() - if err != nil { - t.Fatal(err) - } - if info != v { - t.Errorf(`Info returns %v; want %v`, info, v) - } -} - -var testStoreSrc = map[string]*value{ - "/": {name: ".", mode: fs.ModePerm, isDir: true}, - "/dir0": {name: "dir0", mode: fs.ModePerm, isDir: true}, - "/dir0/file01.txt": {name: "dir0/file01.txt", mode: fs.ModePerm, isDir: false}, - "/dir0/file02.txt": {name: "dir0/file02.txt", mode: fs.ModePerm, isDir: false}, - "/dir1": {name: "dir0", mode: fs.ModePerm, isDir: true}, - "/dir1/file11.txt": {name: "dir1/file11.txt", mode: fs.ModePerm, isDir: false}, - "/dir1/file12.txt": {name: "dir1/file12.txt", mode: fs.ModePerm, isDir: false}, - "/file1.txt": {name: "file1.txt", mode: fs.ModePerm, isDir: false}, - "/file2.txt": {name: "file2.txt", mode: fs.ModePerm, isDir: false}, -} - -func newStoreTest() *store { - s := newStore() - for k, v := range testStoreSrc { - s.put(k, v) - } - return s -} - -func TestStore(t *testing.T) { - s := newStoreTest() - - var wantKeys []string - for k := range testStoreSrc { - wantKeys = append(wantKeys, k) - } - sort.Strings(wantKeys) - - if !reflect.DeepEqual(s.keys, wantKeys) { - t.Errorf(`Error store.keys is %v; want %v`, s.keys, wantKeys) - } - - key := "/dir0/file02.txt" - wantValue := testStoreSrc[key] - gotValue := s.get(key) - if !reflect.DeepEqual(gotValue, wantValue) { - t.Errorf(`Error store.get("%s") returns %v; want %v`, key, gotValue, wantValue) - } -} - -func TestStore_remove(t *testing.T) { - s := newStoreTest() - - key := "/dir1/file11.txt" - v := s.get(key) - if v == nil { - t.Errorf(`Error not found %s`, key) - } - - s.remove(key) - v = s.get(key) - if v != nil { - t.Errorf(`Error found %s: %v`, key, v) - } - - for _, k := range s.keys { - if k == key { - t.Errorf(`Error found %s`, key) - } - } - - v = s.remove(key) - if v != nil { - t.Errorf(`Error found %s: %v`, key, v) - } -} - -func TestStore_removeAll(t *testing.T) { - s := newStoreTest() - - prefix := "/dir0" - s.removeAll(prefix) - - for _, key := range s.keys { - if strings.HasPrefix(key, prefix) { - t.Errorf(`Error found %s`, key) - } - v := s.get(key) - if v == nil { - t.Errorf(`Error not found %s: %v`, key, v) - } - } - - want := len(s.keys) - s.removeAll(prefix) - got := len(s.keys) - - if got != want { - t.Errorf(`Error keys length %d; want %d`, got, want) - } -} - -func TestStore_prefixKeys(t *testing.T) { - testCases := []struct { - want []string - prefix string - }{ - { - want: []string{ - "/dir0", - "/dir1", - "/file1.txt", - "/file2.txt", - }, - prefix: "/", - }, { - want: []string{ - "/dir0/file01.txt", - "/dir0/file02.txt", - }, - prefix: "/dir0", - }, { - prefix: "/not-found", - }, - } - - s := newStoreTest() - for _, tc := range testCases { - got := s.prefixKeys(tc.prefix) - if !reflect.DeepEqual(got, tc.want) { - t.Errorf(`Error prefixKeys("%s") got %v; want %v`, tc.prefix, got, tc.want) - } - } -} - -func TestStore_prefixGlobKeys(t *testing.T) { - testCases := []struct { - want []string - prefix string - pattern string - errStr string - }{ - { - want: []string{ - "/dir0/file01.txt", - "/dir1/file11.txt", - }, - prefix: "/", - pattern: "*/*1.txt", - }, { - want: []string{ - "/dir0/file01.txt", - "/dir0/file02.txt", - }, - prefix: "/dir0", - pattern: "*.txt", - }, { - prefix: "/not-found", - pattern: "*.*", - }, { - prefix: "/", - pattern: "*.go", - }, { - prefix: "/", - pattern: "[[", - errStr: "syntax error in pattern", - }, - } - - s := newStoreTest() - for _, tc := range testCases { - got, err := s.prefixGlobKeys(tc.prefix, tc.pattern) - errStr := "" - if err != nil { - errStr = err.Error() - } - if errStr != tc.errStr { - t.Errorf(`Error prefixGlobKeys("%s", "%s") error got "%s"; want "%s"`, - tc.prefix, tc.pattern, errStr, tc.errStr) - continue - } - if !reflect.DeepEqual(got, tc.want) { - t.Errorf(`Error prefixGlobKeys("%s", "%s") got %v; want %v`, - tc.prefix, tc.pattern, got, tc.want) - } - } -} diff --git a/osfs/example_test.go b/osfs/example_test.go deleted file mode 100644 index e83158a..0000000 --- a/osfs/example_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package osfs_test - -import ( - "fmt" - "io/fs" - "io/ioutil" - "log" - "os" - - "github.com/jarxorg/io2" - "github.com/jarxorg/io2/osfs" -) - -func ExampleDirFS() { - tmpDir, err := ioutil.TempDir("", "example") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - name := "example.txt" - content := []byte(`Hello`) - - fsys := osfs.DirFS(tmpDir) - _, err = io2.WriteFile(fsys, name, content, fs.ModePerm) - if err != nil { - log.Fatal(err) - } - - wrote, err := ioutil.ReadFile(tmpDir + "/" + name) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%s\n", string(wrote)) - - // Output: Hello -} diff --git a/osfs/osfs.go b/osfs/osfs.go deleted file mode 100644 index 84b8197..0000000 --- a/osfs/osfs.go +++ /dev/null @@ -1,156 +0,0 @@ -// Package osfs provides a filesystem for the OS. -package osfs - -import ( - "io/fs" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/jarxorg/io2" -) - -// DirFS returns a filesystem for the tree of files rooted at the directory dir. -// The filesystem can write using io2.WriteFile(fsys fs.FS, name string, p []byte). -func DirFS(dir string) fs.FS { - return New(dir) -} - -// containsDenyWin reports whether any path characters of windows are within s. -func containsDenyWin(s string) bool { - return strings.ContainsAny(s, `\:`) -} - -// isInvalidPath reports whether the given path name is valid for use in a call to Create and Write. -func isInvalidPath(name string) bool { - return !fs.ValidPath(name) || runtime.GOOS == "windows" && containsDenyWin(name) -} - -var osCreateFunc = func(name string) (*os.File, error) { - return os.Create(name) -} - -var osMkdirAllFunc = func(dir string, perm os.FileMode) error { - return os.MkdirAll(dir, perm) -} - -var osRemoveFunc = func(name string) error { - return os.Remove(name) -} - -var osRemoveAllFunc = func(path string) error { - return os.RemoveAll(path) -} - -// OSFS represents a filesystem for the OS. -type OSFS struct { - Dir string - osFS *io2.FSDelegator -} - -var ( - _ fs.FS = (*OSFS)(nil) - _ fs.GlobFS = (*OSFS)(nil) - _ fs.ReadDirFS = (*OSFS)(nil) - _ fs.ReadFileFS = (*OSFS)(nil) - _ fs.StatFS = (*OSFS)(nil) - _ fs.SubFS = (*OSFS)(nil) - _ io2.WriteFileFS = (*OSFS)(nil) - _ io2.RemoveFileFS = (*OSFS)(nil) -) - -// NewOSFS returns a filesystem for the tree of files rooted at the directory dir. -// Deprecated: Use New. -func NewOSFS(dir string) *OSFS { - return New(dir) -} - -// New returns a filesystem for the tree of files rooted at the directory dir. -func New(dir string) *OSFS { - return &OSFS{ - Dir: dir, - osFS: io2.DelegateFS(os.DirFS(dir)), - } -} - -// Open opens the named file. -func (fsys *OSFS) Open(name string) (fs.File, error) { - return fsys.osFS.Open(name) -} - -// Glob returns the names of all files matching pattern, providing an implementation -// of the top-level Glob function. -func (fsys *OSFS) Glob(pattern string) ([]string, error) { - return fsys.osFS.Glob(pattern) -} - -// ReadDir reads the named directory and returns a list of directory entries sorted -// by filename. -func (fsys *OSFS) ReadDir(dir string) ([]fs.DirEntry, error) { - return fsys.osFS.ReadDir(dir) -} - -// ReadFile reads the named file and returns its contents. -func (fsys *OSFS) ReadFile(name string) ([]byte, error) { - return fsys.osFS.ReadFile(name) -} - -// Stat returns a FileInfo describing the file. If there is an error, it should be -// of type *PathError. -func (fsys *OSFS) Stat(name string) (fs.FileInfo, error) { - return fsys.osFS.Stat(name) -} - -// Sub returns an FS corresponding to the subtree rooted at dir. -func (fsys *OSFS) Sub(dir string) (fs.FS, error) { - return NewOSFS(filepath.Join(fsys.Dir, dir)), nil -} - -// MkdirAll creates the named directory. -func (fsys *OSFS) MkdirAll(dir string, mode fs.FileMode) error { - if isInvalidPath(dir) { - return &fs.PathError{Op: "MkdirAll", Path: dir, Err: fs.ErrInvalid} - } - return osMkdirAllFunc(filepath.Join(fsys.Dir, dir), mode) -} - -// CreateFile creates the named file. -func (fsys *OSFS) CreateFile(name string, mode fs.FileMode) (io2.WriterFile, error) { - if isInvalidPath(name) { - return nil, &fs.PathError{Op: "Create", Path: name, Err: fs.ErrInvalid} - } - path := filepath.Join(fsys.Dir, name) - err := osMkdirAllFunc(filepath.Dir(path), mode) - if err != nil { - return nil, err - } - return osCreateFunc(path) -} - -// WriteFile writes the specified bytes to the named file. -func (fsys *OSFS) WriteFile(name string, p []byte, mode fs.FileMode) (int, error) { - f, err := fsys.CreateFile(name, mode) - if err != nil { - return 0, err - } - defer f.Close() - - return f.Write(p) -} - -// RemoveFile removes the specified named file. -func (fsys *OSFS) RemoveFile(name string) error { - if isInvalidPath(name) { - return &fs.PathError{Op: "Remove", Path: name, Err: fs.ErrInvalid} - } - return osRemoveFunc(filepath.Join(fsys.Dir, name)) -} - -// RemoveAll removes path and any children it contains. -func (fsys *OSFS) RemoveAll(path string) error { - if isInvalidPath(path) { - return &fs.PathError{Op: "RemoveAll", Path: path, Err: fs.ErrInvalid} - } - return osRemoveAllFunc(filepath.Join(fsys.Dir, path)) -} diff --git a/osfs/osfs_test.go b/osfs/osfs_test.go deleted file mode 100644 index 1b59034..0000000 --- a/osfs/osfs_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package osfs - -import ( - "errors" - "io/fs" - "io/ioutil" - "os" - "reflect" - "testing" - "testing/fstest" - - "github.com/jarxorg/io2" -) - -func TestFS(t *testing.T) { - if err := fstest.TestFS(DirFS("testdata"), "dir0"); err != nil { - t.Errorf("Error testing/fstest: %+v", err) - } - if err := fstest.TestFS(DirFS("testdata"), "dir0/file01.txt"); err != nil { - t.Errorf("Error testing/fstest: %+v", err) - } -} - -func TestMkdirAll(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - fsys := NewOSFS(tmpDir) - err = fsys.MkdirAll("dir", fs.ModePerm) - if err != nil { - t.Fatal(err) - } - - err = fsys.MkdirAll("../invalid", fs.ModePerm) - if err == nil { - t.Fatal(err) - } -} - -func TestCreateFile(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - fsys := DirFS(tmpDir) - got, err := io2.CreateFile(fsys, "test.txt", fs.ModePerm) - if err != nil { - t.Fatal(err) - } - defer got.Close() -} - -func TestCreateFile_MkdirAllError(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - orgMkdirAllFunc := osMkdirAllFunc - defer func() { osMkdirAllFunc = orgMkdirAllFunc }() - - wantErr := errors.New("test") - osMkdirAllFunc = func(dir string, perm os.FileMode) error { - return wantErr - } - - fsys := DirFS(tmpDir) - var gotErr error - _, gotErr = io2.CreateFile(fsys, "name.txt", fs.ModePerm) - - if !reflect.DeepEqual(gotErr, wantErr) { - t.Errorf("Error CreateFile returns unknown error %v; want %v", gotErr, wantErr) - } -} - -func TestWriteFile(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - name := "test.txt" - want := []byte(`test`) - - fsys := DirFS(tmpDir) - n, err := io2.WriteFile(fsys, name, want, fs.ModePerm) - if err != nil { - t.Fatal(err) - } - if n != len(want) { - t.Errorf("Error len %d; want %d", n, len(want)) - } - - got, err := ioutil.ReadFile(tmpDir + "/" + name) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("Error content %s; want %s", got, want) - } -} - -func TestWriteFile_InvalidError(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - fsys := DirFS(tmpDir) - _, err = io2.WriteFile(fsys, "../invalid.txt", []byte{}, fs.ModePerm) - if err == nil { - t.Fatal("Error WriteFile returns no error") - } -} - -func TestContainsDenyWin(t *testing.T) { - testCases := []struct { - name string - want bool - }{ - { - name: `allow.txt`, - want: false, - }, { - name: `path/to/allow.txt`, - want: false, - }, { - name: `deny:txt`, - want: true, - }, { - name: `C:/deny.txt`, - want: true, - }, { - name: `path\to\deny.txt`, - want: true, - }, - } - for i, testCase := range testCases { - got := containsDenyWin(testCase.name) - if got != testCase.want { - t.Errorf("Error[%d] containsDenyWin(%s) %v; want %v", - i, testCase.name, got, testCase.want) - } - } -} - -func TestSub_WriteFile(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - dir := "sub" - name := "test.txt" - want := []byte(`test`) - - fsys, err := fs.Sub(DirFS(tmpDir), dir) - if err != nil { - t.Fatal(err) - } - n, err := io2.WriteFile(fsys, name, want, fs.ModePerm) - if err != nil { - t.Fatal(err) - } - if n != len(want) { - t.Errorf("Error len %d; want %d", n, len(want)) - } - - got, err := ioutil.ReadFile(tmpDir + "/" + dir + "/" + name) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("Error content %s; want %s", got, want) - } -} - -func TestRemoveFile(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - fsys := DirFS(tmpDir) - name := "test.txt" - - if err = ioutil.WriteFile(tmpDir+"/"+name, []byte{}, fs.ModePerm); err != nil { - t.Fatal(err) - } - - err = io2.RemoveFile(fsys, name) - if err != nil { - t.Fatal(err) - } -} - -func TestRemoveFile_InvalidError(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - fsys := DirFS(tmpDir) - err = io2.RemoveFile(fsys, "../invalid-dir") - if err == nil { - t.Fatal("Error RemoveFile returns no error") - } -} - -func TestRemoveAll(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - fsys := DirFS(tmpDir) - path := "dir" - name := "test.txt" - - if err = os.Mkdir(tmpDir+"/"+path, fs.ModePerm); err != nil { - t.Fatal(err) - } - if err = ioutil.WriteFile(tmpDir+"/"+path+"/"+name, []byte{}, fs.ModePerm); err != nil { - t.Fatal(err) - } - - err = io2.RemoveAll(fsys, path) - if err != nil { - t.Fatal(err) - } -} - -func TestRemoveAll_InvalidError(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - fsys := DirFS(tmpDir) - err = io2.RemoveAll(fsys, "../invalid-dir") - if err == nil { - t.Fatal("Error RemoveAll returns no error") - } -} diff --git a/osfs/testdata/dir0/file01.txt b/osfs/testdata/dir0/file01.txt deleted file mode 100644 index d019c67..0000000 --- a/osfs/testdata/dir0/file01.txt +++ /dev/null @@ -1 +0,0 @@ -content01 diff --git a/osfs/testdata/dir0/file02.txt b/osfs/testdata/dir0/file02.txt deleted file mode 100644 index 1793130..0000000 --- a/osfs/testdata/dir0/file02.txt +++ /dev/null @@ -1 +0,0 @@ -content02