Skip to content

Commit

Permalink
Merge pull request #12 from smola/removeall
Browse files Browse the repository at this point in the history
add RemoveAll
  • Loading branch information
mcuadros authored Feb 20, 2017
2 parents f84fa56 + 3d9b579 commit 9c1722d
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 5 deletions.
77 changes: 77 additions & 0 deletions fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,80 @@ func (f *BaseFile) Filename() string {
func (f *BaseFile) IsClosed() bool {
return f.Closed
}

type removerAll interface {
RemoveAll(string) error
}

// RemoveAll removes path and any children it contains.
// It removes everything it can but returns the first error
// it encounters. If the path does not exist, RemoveAll
// returns nil (no error).
func RemoveAll(fs Filesystem, path string) error {
r, ok := fs.(removerAll)
if ok {
return r.RemoveAll(path)
}

return removeAll(fs, path)
}

func removeAll(fs Filesystem, path string) error {
// This implementation is adapted from os.RemoveAll.

// Simple case: if Remove works, we're done.
err := fs.Remove(path)
if err == nil || os.IsNotExist(err) {
return nil
}

// Otherwise, is this a directory we need to recurse into?
dir, serr := fs.Stat(path)
if serr != nil {
if os.IsNotExist(serr) {
return nil
}

return serr
}

if !dir.IsDir() {
// Not a directory; return the error from Remove.
return err
}

// Directory.
fis, err := fs.ReadDir(path)
if err != nil {
if os.IsNotExist(err) {
// Race. It was deleted between the Lstat and Open.
// Return nil per RemoveAll's docs.
return nil
}

return err
}

// Remove contents & return first error.
err = nil
for _, fi := range fis {
cpath := fs.Join(path, fi.Name())
err1 := removeAll(fs, cpath)
if err == nil {
err = err1
}
}

// Remove directory.
err1 := fs.Remove(path)
if err1 == nil || os.IsNotExist(err1) {
return nil
}

if err == nil {
err = err1
}

return err

}
37 changes: 35 additions & 2 deletions memfs/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"time"
Expand Down Expand Up @@ -78,7 +79,9 @@ func (fs *Memory) Stat(filename string) (billy.FileInfo, error) {

info, err := fs.ReadDir(fullpath)
if err == nil && len(info) != 0 {
return newFileInfo(fs.base, fullpath, len(info)+100), nil
fi := newFileInfo(fs.base, fullpath, len(info))
fi.isDir = true
return fi, nil
}

return nil, os.ErrNotExist
Expand All @@ -90,7 +93,7 @@ func (fs *Memory) ReadDir(base string) (entries []billy.FileInfo, err error) {

appendedDirs := make(map[string]bool, 0)
for fullpath, f := range fs.s.files {
if !strings.HasPrefix(fullpath, base) {
if !isInDir(base, fullpath) {
continue
}

Expand Down Expand Up @@ -158,6 +161,10 @@ func (fs *Memory) Rename(from, to string) error {
func (fs *Memory) Remove(filename string) error {
fullpath := fs.Join(fs.base, filename)
if _, ok := fs.s.files[fullpath]; !ok {
if fs.isDir(fullpath) {
return fmt.Errorf("directory not empty: %s", filename)
}

return os.ErrNotExist
}

Expand All @@ -184,6 +191,16 @@ func (fs *Memory) Base() string {
return fs.base
}

func (fs *Memory) isDir(path string) bool {
for fpath := range fs.s.files {
if isInDir(path, fpath) {
return true
}
}

return false
}

type file struct {
billy.BaseFile

Expand Down Expand Up @@ -375,3 +392,19 @@ func isReadOnly(flag int) bool {
func isWriteOnly(flag int) bool {
return flag&os.O_WRONLY != 0
}

func isInDir(dir, other string) bool {
dir = path.Clean(dir)
dir = toTrailingSlash(dir)
other = path.Clean(other)

return strings.HasPrefix(other, dir)
}

func toTrailingSlash(p string) string {
if strings.HasSuffix(p, "/") {
return p
}

return p + "/"
}
7 changes: 7 additions & 0 deletions osfs/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ func (fs *OS) Base() string {
return fs.base
}

// RemoveAll removes a file or directory recursively. Removes everything it can,
// but returns the first error.
func (fs *OS) RemoveAll(path string) error {
fullpath := fs.Join(fs.base, path)
return os.RemoveAll(fullpath)
}

// osFile represents a file in the os filesystem
type osFile struct {
billy.BaseFile
Expand Down
96 changes: 93 additions & 3 deletions test/fs_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,13 @@ func (s *FilesystemSuite) TestReadDirFileInfoDirs(c *C) {
c.Assert(info[0].Name(), Equals, "foo")
}

func (s *FilesystemSuite) TestStatNonExistent(c *C) {
fi, err := s.Fs.Stat("non-existent")
comment := Commentf("error: %s", err)
c.Assert(os.IsNotExist(err), Equals, true, comment)
c.Assert(fi, IsNil)
}

func (s *FilesystemSuite) TestDirStat(c *C) {
files := []string{"foo", "bar", "qux/baz", "qux/qux"}
for _, name := range files {
Expand All @@ -307,14 +314,28 @@ func (s *FilesystemSuite) TestDirStat(c *C) {
c.Assert(f.Close(), IsNil)
}

// Some implementations detect directories based on a prefix
// for all files; it's easy to miss path separator handling there.
fi, err := s.Fs.Stat("qu")
c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err))
c.Assert(fi, IsNil)

fi, err = s.Fs.Stat("qux")
c.Assert(err, IsNil)
c.Assert(fi.Name(), Equals, "qux")
c.Assert(fi.IsDir(), Equals, true)

qux := s.Fs.Dir("qux")
fi, err := qux.Stat("baz")

fi, err = qux.Stat("baz")
c.Assert(err, IsNil)
c.Assert(fi.Name(), Equals, "baz")
c.Assert(fi.IsDir(), Equals, false)

fi, err = qux.Stat("/baz")
c.Assert(err, IsNil)
c.Assert(fi.Name(), Equals, "baz")
c.Assert(fi.IsDir(), Equals, false)
}

func (s *FilesystemSuite) TestCreateInDir(c *C) {
Expand All @@ -335,7 +356,7 @@ func (s *FilesystemSuite) TestRename(c *C) {

foo, err := s.Fs.Stat("foo")
c.Assert(foo, IsNil)
c.Assert(err, NotNil)
c.Assert(os.IsNotExist(err), Equals, true)

bar, err := s.Fs.Stat("bar")
c.Assert(bar, NotNil)
Expand Down Expand Up @@ -404,7 +425,18 @@ func (s *FilesystemSuite) TestRemove(c *C) {
}

func (s *FilesystemSuite) TestRemoveNonExisting(c *C) {
c.Assert(s.Fs.Remove("NON-EXISTING"), NotNil)
err := s.Fs.Remove("NON-EXISTING")
c.Assert(err, NotNil)
c.Assert(os.IsNotExist(err), Equals, true)
}

func (s *FilesystemSuite) TestRemoveNotEmptyDir(c *C) {
f, err := s.Fs.Create("foo/bar")
c.Assert(err, IsNil)
c.Assert(f.Close(), IsNil)

err = s.Fs.Remove("foo")
c.Assert(err, NotNil)
}

func (s *FilesystemSuite) TestRemoveTempFile(c *C) {
Expand Down Expand Up @@ -479,3 +511,61 @@ func (s *FilesystemSuite) TestReadWriteLargeFile(c *C) {
c.Assert(err, IsNil)
c.Assert(len(b), Equals, size)
}

func (s *FilesystemSuite) TestRemoveAllNonExistent(c *C) {
c.Assert(RemoveAll(s.Fs, "non-existent"), IsNil)
}

func (s *FilesystemSuite) TestRemoveAll(c *C) {
fnames := []string{
"foo/1",
"foo/2",
"foo/bar/1",
"foo/bar/2",
"foo/bar/baz/1",
"foo/bar/baz/qux/1",
"foo/bar/baz/qux/2",
"foo/bar/baz/qux/3",
}

for _, fname := range fnames {
f, err := s.Fs.Create(fname)
c.Assert(err, IsNil)
c.Assert(f.Close(), IsNil)
}

c.Assert(RemoveAll(s.Fs, "foo"), IsNil)

for _, fname := range fnames {
_, err := s.Fs.Stat(fname)
comment := Commentf("not removed: %s %s", fname, err)
c.Assert(os.IsNotExist(err), Equals, true, comment)
}
}

func (s *FilesystemSuite) TestRemoveAllRelative(c *C) {
fnames := []string{
"foo/1",
"foo/2",
"foo/bar/1",
"foo/bar/2",
"foo/bar/baz/1",
"foo/bar/baz/qux/1",
"foo/bar/baz/qux/2",
"foo/bar/baz/qux/3",
}

for _, fname := range fnames {
f, err := s.Fs.Create(fname)
c.Assert(err, IsNil)
c.Assert(f.Close(), IsNil)
}

c.Assert(RemoveAll(s.Fs, "foo/bar/.."), IsNil)

for _, fname := range fnames {
_, err := s.Fs.Stat(fname)
comment := Commentf("not removed: %s %s", fname, err)
c.Assert(os.IsNotExist(err), Equals, true, comment)
}
}

0 comments on commit 9c1722d

Please sign in to comment.