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.
|
||||
```go
|
||||
var AppFs afero.Fs = &afero.MemMapFs{}
|
||||
var AppFs afero.Fs = afero.NewMemMapFs()
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
```go
|
||||
fs := new(afero.MemMapFs)
|
||||
fs := afero.NewMemMapFs
|
||||
afs := &Afero{Fs: fs}
|
||||
f, err := afs.TempFile("", "ioutil-test")
|
||||
```
|
||||
|
@ -187,39 +187,28 @@ backend is perfect for testing.
|
|||
* No test cleanup needed
|
||||
|
||||
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
|
||||
can set it to &afero.MemMapFs{}.
|
||||
In your application this will be set to afero.NewOsFs() during testing you
|
||||
can set it to afero.NewMemMapFs().
|
||||
|
||||
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
|
||||
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:
|
||||
```go
|
||||
func TestExist(t *testing.T) {
|
||||
appFS = &afero.MemMapFs{}
|
||||
appFS = afero.NewMemMapFs()
|
||||
// create test files and directories
|
||||
appFS.MkdirAll("src/a", 0755))
|
||||
appFS.WriteFile("src/a/b", []byte("file b"), 0644)
|
||||
appFS.WriteFile("src/c", []byte("file c"), 0644)
|
||||
testExistence("src/c", true, t)
|
||||
}
|
||||
|
||||
func testExistence(name string, e bool, t *testing.T) {
|
||||
_, err := appFS.Stat(name)
|
||||
_, err := appFS.Stat("src/c")
|
||||
if os.IsNotExist(err) {
|
||||
if e {
|
||||
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)
|
||||
}
|
||||
t.Errorf("file \"%s\" does not exist.\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
# 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
|
||||
operation and a mock filesystem during testing or as needed.
|
||||
|
||||
```go
|
||||
appfs := NewOsFs()
|
||||
appfs.MkdirAll("src/a", 0755))
|
||||
```
|
||||
|
||||
## Memory Backed Storage
|
||||
|
||||
### 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
|
||||
safely.
|
||||
|
||||
```go
|
||||
mm := NewMemMapFs()
|
||||
mm.MkdirAll("src/a", 0755))
|
||||
```
|
||||
|
||||
#### InMemoryFile
|
||||
|
||||
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.
|
||||
|
||||
```go
|
||||
bp := &BasePathFs{source: &OsFs{}, path: "/base/path"}
|
||||
bp := NewBasePathFs(NewOsFs(), "/base/path")
|
||||
```
|
||||
|
||||
### ReadOnlyFs
|
||||
|
@ -273,7 +272,7 @@ bp := &BasePathFs{source: &OsFs{}, path: "/base/path"}
|
|||
A thin wrapper around the source Fs providing a read only view.
|
||||
|
||||
```go
|
||||
fs := &ReadOnlyFs{source: &OsFs{}}
|
||||
fs := NewReadOnlyFs(NewOsFs())
|
||||
_, err := fs.Create("/file.txt")
|
||||
// err = syscall.EPERM
|
||||
```
|
||||
|
@ -286,7 +285,7 @@ Files not matching the regexp provided will not be created.
|
|||
Directories are not filtered.
|
||||
|
||||
```go
|
||||
fs := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: &MemMapFs{}}
|
||||
fs := NewRegexpFs(NewMemMapFs(), regexp.MustCompile(`\.txt$`))
|
||||
_, err := fs.Create("/file.html")
|
||||
// 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.
|
||||
|
||||
```go
|
||||
httpFs := &afero.HttpFs{source: <ExistingFS>}
|
||||
httpFs := afero.NewHttpFs(<ExistingFS>)
|
||||
fileserver := http.FileServer(httpFs.Dir(<PATH>)))
|
||||
http.Handle("/", fileserver)
|
||||
```
|
||||
|
@ -335,9 +334,9 @@ from the base to the overlay when they're not present (or outdated) in the
|
|||
caching layer.
|
||||
|
||||
```go
|
||||
base := &OsFs{}
|
||||
layer := &MemMapFs{}
|
||||
ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 100 * time.Second}
|
||||
base := NewOsFs()
|
||||
layer := NewMemMapFs()
|
||||
ufs := NewCacheOnReadFs(base, layer, 100 * time.Second)
|
||||
```
|
||||
|
||||
### CopyOnWriteFs()
|
||||
|
@ -361,17 +360,17 @@ overlay will be removed/renamed.
|
|||
The writable overlay layer is currently limited to MemMapFs.
|
||||
|
||||
```go
|
||||
base := &OsFs{}
|
||||
roBase := &ReadOnlyFs{source: base}
|
||||
ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}}
|
||||
base := NewOsFs()
|
||||
roBase := NewReadOnlyFs(base)
|
||||
ufs := NewCopyOnWriteFs(roBase, NewMemMapFs())
|
||||
|
||||
fh, _ = ufs.Create("/home/test/file2.txt")
|
||||
fh.WriteString("This is a test")
|
||||
fh.Close()
|
||||
```
|
||||
|
||||
In this example all write operations will only occur in memory (&MemMapFs{})
|
||||
leaving the base filesystem (&OsFs) untouched.
|
||||
In this example all write operations will only occur in memory (MemMapFs)
|
||||
leaving the base filesystem (OsFs) untouched.
|
||||
|
||||
|
||||
## Desired/possible backends
|
||||
|
|
|
@ -20,6 +20,10 @@ type BasePathFs struct {
|
|||
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,
|
||||
// else the given file with the base path prepended
|
||||
func (b *BasePathFs) RealPath(name string) (path string, err error) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
func TestBasePath(t *testing.T) {
|
||||
baseFs := &MemMapFs{}
|
||||
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 {
|
||||
t.Errorf("Failed to set real path")
|
||||
|
|
13
httpFs.go
13
httpFs.go
|
@ -20,6 +20,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type httpDir struct {
|
||||
|
@ -48,6 +49,10 @@ type HttpFs struct {
|
|||
source Fs
|
||||
}
|
||||
|
||||
func NewHttpFs(source Fs) *HttpFs {
|
||||
return &HttpFs{source: source}
|
||||
}
|
||||
|
||||
func (h HttpFs) Dir(s string) *httpDir {
|
||||
return &httpDir{basePath: s, fs: h}
|
||||
}
|
||||
|
@ -58,6 +63,14 @@ func (h HttpFs) Create(name string) (File, error) {
|
|||
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 {
|
||||
return h.source.Mkdir(name, perm)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,10 @@ type MemMapFs struct {
|
|||
init sync.Once
|
||||
}
|
||||
|
||||
func NewMemMapFs() Fs {
|
||||
return &MemMapFs{}
|
||||
}
|
||||
|
||||
var memfsInit sync.Once
|
||||
|
||||
func (m *MemMapFs) getData() map[string]*mem.FileData {
|
||||
|
|
|
@ -34,7 +34,7 @@ func TestNormalizePath(t *testing.T) {
|
|||
func TestPathErrors(t *testing.T) {
|
||||
path := filepath.Join(".", "some", "path")
|
||||
path2 := filepath.Join(".", "different", "path")
|
||||
fs := &MemMapFs{}
|
||||
fs := NewMemMapFs()
|
||||
perm := os.FileMode(0755)
|
||||
|
||||
// relevant functions:
|
||||
|
|
4
os.go
4
os.go
|
@ -25,6 +25,10 @@ import (
|
|||
// (http://golang.org/pkg/os/).
|
||||
type OsFs struct{}
|
||||
|
||||
func NewOsFs() Fs {
|
||||
return &OsFs{}
|
||||
}
|
||||
|
||||
func (OsFs) Name() string { return "OsFs" }
|
||||
|
||||
func (OsFs) Create(name string) (File, error) {
|
||||
|
|
|
@ -10,6 +10,10 @@ type ReadOnlyFs struct {
|
|||
source Fs
|
||||
}
|
||||
|
||||
func NewReadOnlyFs(source Fs) Fs {
|
||||
return &ReadOnlyFs{source: source}
|
||||
}
|
||||
|
||||
func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) {
|
||||
return ReadDir(r.source, name)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ type RegexpFs struct {
|
|||
source Fs
|
||||
}
|
||||
|
||||
func NewRegexpFs(source Fs, re *regexp.Regexp) Fs {
|
||||
return &RegexpFs{source: source, re: re}
|
||||
}
|
||||
|
||||
type RegexpFile struct {
|
||||
f File
|
||||
re *regexp.Regexp
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestFilterReadonlyRemoveAndRead(t *testing.T) {
|
|||
fh.Write([]byte("content here"))
|
||||
fh.Close()
|
||||
|
||||
fs := &ReadOnlyFs{source: mfs}
|
||||
fs := NewReadOnlyFs(mfs)
|
||||
err = fs.Remove("/file.txt")
|
||||
if err == nil {
|
||||
t.Errorf("Did not fail to remove file")
|
||||
|
@ -51,7 +51,7 @@ func TestFilterReadonlyRemoveAndRead(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")
|
||||
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
|
||||
// by the number of bytes read.
|
||||
type UnionFile struct {
|
||||
layer File
|
||||
base File
|
||||
layer File
|
||||
off int
|
||||
files []os.FileInfo
|
||||
}
|
||||
|
|
|
@ -25,6 +25,10 @@ type CacheOnReadFs struct {
|
|||
cacheTime time.Duration
|
||||
}
|
||||
|
||||
func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs {
|
||||
return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime}
|
||||
}
|
||||
|
||||
type cacheState int
|
||||
|
||||
const (
|
||||
|
|
|
@ -22,6 +22,10 @@ type CopyOnWriteFs struct {
|
|||
layer Fs
|
||||
}
|
||||
|
||||
func NewCopyOnWriteFs(base Fs, layer Fs) Fs {
|
||||
return &CopyOnWriteFs{base: base, layer: layer}
|
||||
}
|
||||
|
||||
func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) {
|
||||
if _, err := u.layer.Stat(name); err == nil {
|
||||
return false, nil
|
||||
|
|
|
@ -11,7 +11,7 @@ func TestUnionCreateExisting(t *testing.T) {
|
|||
base := &MemMapFs{}
|
||||
roBase := &ReadOnlyFs{source: base}
|
||||
|
||||
ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}}
|
||||
ufs := NewCopyOnWriteFs(roBase, &MemMapFs{})
|
||||
|
||||
base.MkdirAll("/home/test", 0777)
|
||||
fh, _ := base.Create("/home/test/file.txt")
|
||||
|
@ -86,7 +86,7 @@ func TestUnionCacheWrite(t *testing.T) {
|
|||
base := &MemMapFs{}
|
||||
layer := &MemMapFs{}
|
||||
|
||||
ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 0}
|
||||
ufs := NewCacheOnReadFs(base, layer, 0)
|
||||
|
||||
base.Mkdir("/data", 0777)
|
||||
|
||||
|
|
Loading…
Reference in New Issue