diff --git a/fs.go b/fs.go index 042d3d7..77cf25f 100644 --- a/fs.go +++ b/fs.go @@ -21,6 +21,7 @@ var ( // * Get a temporal file. // * Rename files. // * Remove files. +// * Create directories. // * Join parts of path. // * Obtain a filesystem starting on a subdirectory in the current filesystem. // * Get the base path for the filesystem. @@ -35,6 +36,7 @@ type Filesystem interface { TempFile(dir, prefix string) (File, error) Rename(from, to string) error Remove(filename string) error + MkdirAll(filename string, perm os.FileMode) error Join(elem ...string) string Dir(path string) Filesystem Base() string diff --git a/memfs/memory.go b/memfs/memory.go index dcafd71..08b701b 100644 --- a/memfs/memory.go +++ b/memfs/memory.go @@ -46,15 +46,19 @@ func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.F fullpath := fs.Join(fs.base, filename) f, ok := fs.s.files[fullpath] - if !ok && !isCreate(flag) { - return nil, os.ErrNotExist - } + if !ok { + if !isCreate(flag) { + return nil, os.ErrNotExist + } - if f == nil { fs.s.files[fullpath] = newFile(fs.base, fullpath, flag) return fs.s.files[fullpath], nil } + if f.isDir { + return nil, fmt.Errorf("cannot open directory: %s", filename) + } + n := newFile(fs.base, fullpath, flag) n.content = f.content @@ -73,12 +77,13 @@ func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.F func (fs *Memory) Stat(filename string) (billy.FileInfo, error) { fullpath := fs.Join(fs.base, filename) - if _, ok := fs.s.files[fullpath]; ok { + f, ok := fs.s.files[fullpath] + if ok && !f.isDir { return newFileInfo(fs.base, fullpath, fs.s.files[fullpath].content.Len()), nil } info, err := fs.ReadDir(fullpath) - if err == nil && len(info) != 0 { + if err == nil && len(info) != 0 || f != nil && f.isDir { fi := newFileInfo(fs.base, fullpath, len(info)) fi.isDir = true return fi, nil @@ -101,6 +106,10 @@ func (fs *Memory) ReadDir(base string) (entries []billy.FileInfo, err error) { parts := strings.Split(fullpath, string(separator)) if len(parts) == 1 { + if f.isDir { + entries = append(entries, &fileInfo{name: parts[0], isDir: true}) + } + entries = append(entries, &fileInfo{name: parts[0], size: f.content.Len()}) continue } @@ -116,6 +125,22 @@ func (fs *Memory) ReadDir(base string) (entries []billy.FileInfo, err error) { return } +// MkdirAll creates a directory. +func (fs *Memory) MkdirAll(path string, perm os.FileMode) error { + fullpath := fs.Join(fs.base, path) + f, ok := fs.s.files[fullpath] + if ok { + if !f.isDir { + return fmt.Errorf("%s is a file", path) + } + + return nil + } + + fs.s.files[fullpath] = &file{isDir: true} + return nil +} + var maxTempFiles = 1024 * 4 // TempFile creates a new temporary file. @@ -207,6 +232,7 @@ type file struct { content *content position int64 flag int + isDir bool } func newFile(base, fullpath string, flag int) *file { diff --git a/osfs/os.go b/osfs/os.go index 55bd638..71e9cd7 100644 --- a/osfs/os.go +++ b/osfs/os.go @@ -10,6 +10,11 @@ import ( "srcd.works/go-billy.v1" ) +const ( + defaultDirectoryMode = 0755 + defaultCreateMode = 0666 +) + // OS is a filesystem based on the os filesystem type OS struct { base string @@ -25,7 +30,7 @@ func New(baseDir string) *OS { // Create creates a file and opens it with standard permissions // and modes O_RDWR, O_CREATE and O_TRUNC. func (fs *OS) Create(filename string) (billy.File, error) { - return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) } // OpenFile is equivalent to standard os.OpenFile. @@ -55,7 +60,7 @@ func (fs *OS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, func (fs *OS) createDir(fullpath string) error { dir := filepath.Dir(fullpath) if dir != "." { - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { return err } } @@ -93,6 +98,12 @@ func (fs *OS) Rename(from, to string) error { return os.Rename(from, to) } +// MkdirAll creates a directory. +func (fs *OS) MkdirAll(path string, perm os.FileMode) error { + fullpath := fs.Join(fs.base, path) + return os.MkdirAll(fullpath, defaultDirectoryMode) +} + // Open opens a file in read-only mode. func (fs *OS) Open(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDONLY, 0) diff --git a/test/fs_suite.go b/test/fs_suite.go index d799ba7..e0b4a88 100644 --- a/test/fs_suite.go +++ b/test/fs_suite.go @@ -516,6 +516,14 @@ func (s *FilesystemSuite) TestRemoveAllNonExistent(c *C) { c.Assert(RemoveAll(s.Fs, "non-existent"), IsNil) } +func (s *FilesystemSuite) TestRemoveAllEmptyDir(c *C) { + c.Assert(s.Fs.MkdirAll("empty", os.FileMode(0755)), IsNil) + c.Assert(RemoveAll(s.Fs, "empty"), IsNil) + _, err := s.Fs.Stat("empty") + c.Assert(err, NotNil) + c.Assert(os.IsNotExist(err), Equals, true) +} + func (s *FilesystemSuite) TestRemoveAll(c *C) { fnames := []string{ "foo/1", @@ -569,3 +577,60 @@ func (s *FilesystemSuite) TestRemoveAllRelative(c *C) { c.Assert(os.IsNotExist(err), Equals, true, comment) } } + +func (s *FilesystemSuite) TestMkdirAll(c *C) { + err := s.Fs.MkdirAll("empty", os.FileMode(0755)) + c.Assert(err, IsNil) + fi, err := s.Fs.Stat("empty") + c.Assert(err, IsNil) + c.Assert(fi.IsDir(), Equals, true) +} + +func (s *FilesystemSuite) TestMkdirAllNested(c *C) { + err := s.Fs.MkdirAll("foo/bar/baz", os.FileMode(0755)) + c.Assert(err, IsNil) + fi, err := s.Fs.Stat("foo/bar/baz") + c.Assert(err, IsNil) + c.Assert(fi.IsDir(), Equals, true) +} + +func (s *FilesystemSuite) TestMkdirAllIdempotent(c *C) { + err := s.Fs.MkdirAll("empty", os.FileMode(0755)) + c.Assert(err, IsNil) + fi, err := s.Fs.Stat("empty") + c.Assert(err, IsNil) + c.Assert(fi.IsDir(), Equals, true) + + // idempotent + err = s.Fs.MkdirAll("empty", os.FileMode(0755)) + c.Assert(err, IsNil) + fi, err = s.Fs.Stat("empty") + c.Assert(err, IsNil) + c.Assert(fi.IsDir(), Equals, true) +} + +func (s *FilesystemSuite) TestMkdirAllAndOpenFile(c *C) { + err := s.Fs.MkdirAll("dir", os.FileMode(0755)) + c.Assert(err, IsNil) + + f, err := s.Fs.Create("dir/bar/foo") + c.Assert(err, IsNil) + c.Assert(f.Close(), IsNil) + + fi, err := s.Fs.Stat("dir/bar/foo") + c.Assert(err, IsNil) + c.Assert(fi.IsDir(), Equals, false) +} + +func (s *FilesystemSuite) TestMkdirAllWithExistingFile(c *C) { + f, err := s.Fs.Create("dir/foo") + c.Assert(err, IsNil) + c.Assert(f.Close(), IsNil) + + err = s.Fs.MkdirAll("dir/foo", os.FileMode(0755)) + c.Assert(err, NotNil) + + fi, err := s.Fs.Stat("dir/foo") + c.Assert(err, IsNil) + c.Assert(fi.IsDir(), Equals, false) +}