forked from mirror/afero
Migrate all backends & readme to use constructorish New...
This commit is contained in:
parent
4f6cfb713a
commit
fe8e895336
65
README.md
65
README.md
|
@ -61,11 +61,11 @@ import "github.com/spf13/afero"
|
||||||
|
|
||||||
First define a package variable and set it to a pointer to a filesystem.
|
First define a package variable and set it to a pointer to a filesystem.
|
||||||
```go
|
```go
|
||||||
var AppFs afero.Fs = &afero.MemMapFs{}
|
var AppFs afero.Fs = afero.NewMemMapFs()
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
var AppFs afero.Fs = &afero.OsFs{}
|
var AppFs afero.Fs = afero.NewOsFs()
|
||||||
```
|
```
|
||||||
It is important to note that if you repeat the composite literal you
|
It is important to note that if you repeat the composite literal you
|
||||||
will be using a completely new and isolated filesystem. In the case of
|
will be using a completely new and isolated filesystem. In the case of
|
||||||
|
@ -166,7 +166,7 @@ f, err := afero.TempFile(fs,"", "ioutil-test")
|
||||||
### Calling via Afero
|
### Calling via Afero
|
||||||
|
|
||||||
```go
|
```go
|
||||||
fs := new(afero.MemMapFs)
|
fs := afero.NewMemMapFs
|
||||||
afs := &Afero{Fs: fs}
|
afs := &Afero{Fs: fs}
|
||||||
f, err := afs.TempFile("", "ioutil-test")
|
f, err := afs.TempFile("", "ioutil-test")
|
||||||
```
|
```
|
||||||
|
@ -187,39 +187,28 @@ backend is perfect for testing.
|
||||||
* No test cleanup needed
|
* No test cleanup needed
|
||||||
|
|
||||||
One way to accomplish this is to define a variable as mentioned above.
|
One way to accomplish this is to define a variable as mentioned above.
|
||||||
In your application this will be set to &afero.OsFs{} during testing you
|
In your application this will be set to afero.NewOsFs() during testing you
|
||||||
can set it to &afero.MemMapFs{}.
|
can set it to afero.NewMemMapFs().
|
||||||
|
|
||||||
It wouldn't be uncommon to have each test initialize a blank slate memory
|
It wouldn't be uncommon to have each test initialize a blank slate memory
|
||||||
backend. To do this I would define my `appFS = &afero.OsFs{}` somewhere
|
backend. To do this I would define my `appFS = afero.NewOsFs()` somewhere
|
||||||
appropriate in my application code. This approach ensures that Tests are order
|
appropriate in my application code. This approach ensures that Tests are order
|
||||||
independent, with no test relying on the state left by an earlier test.
|
independent, with no test relying on the state left by an earlier test.
|
||||||
|
|
||||||
Then in my tests I would initialize a new MemMapFs for each test:
|
Then in my tests I would initialize a new MemMapFs for each test:
|
||||||
```go
|
```go
|
||||||
func TestExist(t *testing.T) {
|
func TestExist(t *testing.T) {
|
||||||
appFS = &afero.MemMapFs{}
|
appFS = afero.NewMemMapFs()
|
||||||
// create test files and directories
|
// create test files and directories
|
||||||
appFS.MkdirAll("src/a", 0755))
|
appFS.MkdirAll("src/a", 0755))
|
||||||
appFS.WriteFile("src/a/b", []byte("file b"), 0644)
|
appFS.WriteFile("src/a/b", []byte("file b"), 0644)
|
||||||
appFS.WriteFile("src/c", []byte("file c"), 0644)
|
appFS.WriteFile("src/c", []byte("file c"), 0644)
|
||||||
testExistence("src/c", true, t)
|
_, err := appFS.Stat("src/c")
|
||||||
}
|
|
||||||
|
|
||||||
func testExistence(name string, e bool, t *testing.T) {
|
|
||||||
_, err := appFS.Stat(name)
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
if e {
|
t.Errorf("file \"%s\" does not exist.\n", name)
|
||||||
t.Errorf("file \"%s\" does not exist.\n", name)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
if !e {
|
|
||||||
t.Errorf("file \"%s\" exists.\n", name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Available Backends
|
# Available Backends
|
||||||
|
@ -233,6 +222,11 @@ very easy to use as all of the calls are the same as the existing OS
|
||||||
calls. It also makes it trivial to have your code use the OS during
|
calls. It also makes it trivial to have your code use the OS during
|
||||||
operation and a mock filesystem during testing or as needed.
|
operation and a mock filesystem during testing or as needed.
|
||||||
|
|
||||||
|
```go
|
||||||
|
appfs := NewOsFs()
|
||||||
|
appfs.MkdirAll("src/a", 0755))
|
||||||
|
```
|
||||||
|
|
||||||
## Memory Backed Storage
|
## Memory Backed Storage
|
||||||
|
|
||||||
### MemMapFs
|
### MemMapFs
|
||||||
|
@ -242,6 +236,11 @@ mocking and to speed up unnecessary disk io when persistence isn’t
|
||||||
necessary. It is fully concurrent and will work within go routines
|
necessary. It is fully concurrent and will work within go routines
|
||||||
safely.
|
safely.
|
||||||
|
|
||||||
|
```go
|
||||||
|
mm := NewMemMapFs()
|
||||||
|
mm.MkdirAll("src/a", 0755))
|
||||||
|
```
|
||||||
|
|
||||||
#### InMemoryFile
|
#### InMemoryFile
|
||||||
|
|
||||||
As part of MemMapFs, Afero also provides an atomic, fully concurrent memory
|
As part of MemMapFs, Afero also provides an atomic, fully concurrent memory
|
||||||
|
@ -265,7 +264,7 @@ The given file name to the operations on this Fs will be prepended with
|
||||||
the base path before calling the source Fs.
|
the base path before calling the source Fs.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
bp := &BasePathFs{source: &OsFs{}, path: "/base/path"}
|
bp := NewBasePathFs(NewOsFs(), "/base/path")
|
||||||
```
|
```
|
||||||
|
|
||||||
### ReadOnlyFs
|
### ReadOnlyFs
|
||||||
|
@ -273,7 +272,7 @@ bp := &BasePathFs{source: &OsFs{}, path: "/base/path"}
|
||||||
A thin wrapper around the source Fs providing a read only view.
|
A thin wrapper around the source Fs providing a read only view.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
fs := &ReadOnlyFs{source: &OsFs{}}
|
fs := NewReadOnlyFs(NewOsFs())
|
||||||
_, err := fs.Create("/file.txt")
|
_, err := fs.Create("/file.txt")
|
||||||
// err = syscall.EPERM
|
// err = syscall.EPERM
|
||||||
```
|
```
|
||||||
|
@ -286,7 +285,7 @@ Files not matching the regexp provided will not be created.
|
||||||
Directories are not filtered.
|
Directories are not filtered.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
fs := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: &MemMapFs{}}
|
fs := NewRegexpFs(NewMemMapFs(), regexp.MustCompile(`\.txt$`))
|
||||||
_, err := fs.Create("/file.html")
|
_, err := fs.Create("/file.html")
|
||||||
// err = syscall.ENOENT
|
// err = syscall.ENOENT
|
||||||
```
|
```
|
||||||
|
@ -303,7 +302,7 @@ Afero provides an httpFs file system which satisfies this requirement.
|
||||||
Any Afero FileSystem can be used as an httpFs.
|
Any Afero FileSystem can be used as an httpFs.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
httpFs := &afero.HttpFs{source: <ExistingFS>}
|
httpFs := afero.NewHttpFs(<ExistingFS>)
|
||||||
fileserver := http.FileServer(httpFs.Dir(<PATH>)))
|
fileserver := http.FileServer(httpFs.Dir(<PATH>)))
|
||||||
http.Handle("/", fileserver)
|
http.Handle("/", fileserver)
|
||||||
```
|
```
|
||||||
|
@ -335,9 +334,9 @@ from the base to the overlay when they're not present (or outdated) in the
|
||||||
caching layer.
|
caching layer.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
base := &OsFs{}
|
base := NewOsFs()
|
||||||
layer := &MemMapFs{}
|
layer := NewMemMapFs()
|
||||||
ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 100 * time.Second}
|
ufs := NewCacheOnReadFs(base, layer, 100 * time.Second)
|
||||||
```
|
```
|
||||||
|
|
||||||
### CopyOnWriteFs()
|
### CopyOnWriteFs()
|
||||||
|
@ -361,17 +360,17 @@ overlay will be removed/renamed.
|
||||||
The writable overlay layer is currently limited to MemMapFs.
|
The writable overlay layer is currently limited to MemMapFs.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
base := &OsFs{}
|
base := NewOsFs()
|
||||||
roBase := &ReadOnlyFs{source: base}
|
roBase := NewReadOnlyFs(base)
|
||||||
ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}}
|
ufs := NewCopyOnWriteFs(roBase, NewMemMapFs())
|
||||||
|
|
||||||
fh, _ = ufs.Create("/home/test/file2.txt")
|
fh, _ = ufs.Create("/home/test/file2.txt")
|
||||||
fh.WriteString("This is a test")
|
fh.WriteString("This is a test")
|
||||||
fh.Close()
|
fh.Close()
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example all write operations will only occur in memory (&MemMapFs{})
|
In this example all write operations will only occur in memory (MemMapFs)
|
||||||
leaving the base filesystem (&OsFs) untouched.
|
leaving the base filesystem (OsFs) untouched.
|
||||||
|
|
||||||
|
|
||||||
## Desired/possible backends
|
## Desired/possible backends
|
||||||
|
|
|
@ -20,6 +20,10 @@ type BasePathFs struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewBasePathFs(source Fs, path string) Fs {
|
||||||
|
return &BasePathFs{source: source, path: path}
|
||||||
|
}
|
||||||
|
|
||||||
// on a file outside the base path it returns the given file name and an error,
|
// on a file outside the base path it returns the given file name and an error,
|
||||||
// else the given file with the base path prepended
|
// else the given file with the base path prepended
|
||||||
func (b *BasePathFs) RealPath(name string) (path string, err error) {
|
func (b *BasePathFs) RealPath(name string) (path string, err error) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
func TestBasePath(t *testing.T) {
|
func TestBasePath(t *testing.T) {
|
||||||
baseFs := &MemMapFs{}
|
baseFs := &MemMapFs{}
|
||||||
baseFs.MkdirAll("/base/path/tmp", 0777)
|
baseFs.MkdirAll("/base/path/tmp", 0777)
|
||||||
bp := &BasePathFs{source: baseFs, path: "/base/path"}
|
bp := NewBasePathFs(baseFs, "/base/path")
|
||||||
|
|
||||||
if _, err := bp.Create("/tmp/foo"); err != nil {
|
if _, err := bp.Create("/tmp/foo"); err != nil {
|
||||||
t.Errorf("Failed to set real path")
|
t.Errorf("Failed to set real path")
|
||||||
|
|
13
httpFs.go
13
httpFs.go
|
@ -20,6 +20,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpDir struct {
|
type httpDir struct {
|
||||||
|
@ -48,6 +49,10 @@ type HttpFs struct {
|
||||||
source Fs
|
source Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewHttpFs(source Fs) *HttpFs {
|
||||||
|
return &HttpFs{source: source}
|
||||||
|
}
|
||||||
|
|
||||||
func (h HttpFs) Dir(s string) *httpDir {
|
func (h HttpFs) Dir(s string) *httpDir {
|
||||||
return &httpDir{basePath: s, fs: h}
|
return &httpDir{basePath: s, fs: h}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +63,14 @@ func (h HttpFs) Create(name string) (File, error) {
|
||||||
return h.source.Create(name)
|
return h.source.Create(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Chmod(name string, mode os.FileMode) error {
|
||||||
|
return h.source.Chmod(name, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HttpFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return h.source.Chtimes(name, atime, mtime)
|
||||||
|
}
|
||||||
|
|
||||||
func (h HttpFs) Mkdir(name string, perm os.FileMode) error {
|
func (h HttpFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
return h.source.Mkdir(name, perm)
|
return h.source.Mkdir(name, perm)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,10 @@ type MemMapFs struct {
|
||||||
init sync.Once
|
init sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewMemMapFs() Fs {
|
||||||
|
return &MemMapFs{}
|
||||||
|
}
|
||||||
|
|
||||||
var memfsInit sync.Once
|
var memfsInit sync.Once
|
||||||
|
|
||||||
func (m *MemMapFs) getData() map[string]*mem.FileData {
|
func (m *MemMapFs) getData() map[string]*mem.FileData {
|
||||||
|
|
|
@ -34,7 +34,7 @@ func TestNormalizePath(t *testing.T) {
|
||||||
func TestPathErrors(t *testing.T) {
|
func TestPathErrors(t *testing.T) {
|
||||||
path := filepath.Join(".", "some", "path")
|
path := filepath.Join(".", "some", "path")
|
||||||
path2 := filepath.Join(".", "different", "path")
|
path2 := filepath.Join(".", "different", "path")
|
||||||
fs := &MemMapFs{}
|
fs := NewMemMapFs()
|
||||||
perm := os.FileMode(0755)
|
perm := os.FileMode(0755)
|
||||||
|
|
||||||
// relevant functions:
|
// relevant functions:
|
||||||
|
|
4
os.go
4
os.go
|
@ -25,6 +25,10 @@ import (
|
||||||
// (http://golang.org/pkg/os/).
|
// (http://golang.org/pkg/os/).
|
||||||
type OsFs struct{}
|
type OsFs struct{}
|
||||||
|
|
||||||
|
func NewOsFs() Fs {
|
||||||
|
return &OsFs{}
|
||||||
|
}
|
||||||
|
|
||||||
func (OsFs) Name() string { return "OsFs" }
|
func (OsFs) Name() string { return "OsFs" }
|
||||||
|
|
||||||
func (OsFs) Create(name string) (File, error) {
|
func (OsFs) Create(name string) (File, error) {
|
||||||
|
|
|
@ -10,6 +10,10 @@ type ReadOnlyFs struct {
|
||||||
source Fs
|
source Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewReadOnlyFs(source Fs) Fs {
|
||||||
|
return &ReadOnlyFs{source: source}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) {
|
func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) {
|
||||||
return ReadDir(r.source, name)
|
return ReadDir(r.source, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@ type RegexpFs struct {
|
||||||
source Fs
|
source Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRegexpFs(source Fs, re *regexp.Regexp) Fs {
|
||||||
|
return &RegexpFs{source: source, re: re}
|
||||||
|
}
|
||||||
|
|
||||||
type RegexpFile struct {
|
type RegexpFile struct {
|
||||||
f File
|
f File
|
||||||
re *regexp.Regexp
|
re *regexp.Regexp
|
||||||
|
|
|
@ -20,7 +20,7 @@ func TestFilterReadonlyRemoveAndRead(t *testing.T) {
|
||||||
fh.Write([]byte("content here"))
|
fh.Write([]byte("content here"))
|
||||||
fh.Close()
|
fh.Close()
|
||||||
|
|
||||||
fs := &ReadOnlyFs{source: mfs}
|
fs := NewReadOnlyFs(mfs)
|
||||||
err = fs.Remove("/file.txt")
|
err = fs.Remove("/file.txt")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Did not fail to remove file")
|
t.Errorf("Did not fail to remove file")
|
||||||
|
@ -51,7 +51,7 @@ func TestFilterReadonlyRemoveAndRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterRegexp(t *testing.T) {
|
func TestFilterRegexp(t *testing.T) {
|
||||||
fs := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: &MemMapFs{}}
|
fs := NewRegexpFs(&MemMapFs{}, regexp.MustCompile(`\.txt$`))
|
||||||
_, err := fs.Create("/file.html")
|
_, err := fs.Create("/file.html")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
||||||
|
|
2
union.go
2
union.go
|
@ -21,8 +21,8 @@ import (
|
||||||
// successful read in the overlay will move the cursor position in the base layer
|
// successful read in the overlay will move the cursor position in the base layer
|
||||||
// by the number of bytes read.
|
// by the number of bytes read.
|
||||||
type UnionFile struct {
|
type UnionFile struct {
|
||||||
layer File
|
|
||||||
base File
|
base File
|
||||||
|
layer File
|
||||||
off int
|
off int
|
||||||
files []os.FileInfo
|
files []os.FileInfo
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,10 @@ type CacheOnReadFs struct {
|
||||||
cacheTime time.Duration
|
cacheTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs {
|
||||||
|
return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime}
|
||||||
|
}
|
||||||
|
|
||||||
type cacheState int
|
type cacheState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -22,6 +22,10 @@ type CopyOnWriteFs struct {
|
||||||
layer Fs
|
layer Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewCopyOnWriteFs(base Fs, layer Fs) Fs {
|
||||||
|
return &CopyOnWriteFs{base: base, layer: layer}
|
||||||
|
}
|
||||||
|
|
||||||
func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) {
|
func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) {
|
||||||
if _, err := u.layer.Stat(name); err == nil {
|
if _, err := u.layer.Stat(name); err == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
|
@ -11,7 +11,7 @@ func TestUnionCreateExisting(t *testing.T) {
|
||||||
base := &MemMapFs{}
|
base := &MemMapFs{}
|
||||||
roBase := &ReadOnlyFs{source: base}
|
roBase := &ReadOnlyFs{source: base}
|
||||||
|
|
||||||
ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}}
|
ufs := NewCopyOnWriteFs(roBase, &MemMapFs{})
|
||||||
|
|
||||||
base.MkdirAll("/home/test", 0777)
|
base.MkdirAll("/home/test", 0777)
|
||||||
fh, _ := base.Create("/home/test/file.txt")
|
fh, _ := base.Create("/home/test/file.txt")
|
||||||
|
@ -86,7 +86,7 @@ func TestUnionCacheWrite(t *testing.T) {
|
||||||
base := &MemMapFs{}
|
base := &MemMapFs{}
|
||||||
layer := &MemMapFs{}
|
layer := &MemMapFs{}
|
||||||
|
|
||||||
ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 0}
|
ufs := NewCacheOnReadFs(base, layer, 0)
|
||||||
|
|
||||||
base.Mkdir("/data", 0777)
|
base.Mkdir("/data", 0777)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue