diff --git a/dir.go b/dir.go index e698e38..fefe872 100644 --- a/dir.go +++ b/dir.go @@ -217,6 +217,55 @@ func (d *dir) MkdirAll(path string, perm fs.FileMode) error { return d.dirs[parts[0]].MkdirAll(strings.Join(parts[1:], separator), perm) } +func (d *dir) Mkdir(path string, perm fs.FileMode) error { + parts := strings.Split(path, separator) + + if path == "" { + return fs.ErrExist + } + + d.RLock() + _, fOk := d.files[parts[0]] + _, dOk := d.dirs[parts[0]] + d.RUnlock() + if fOk { + return fs.ErrExist + } + if !dOk && len(parts) > 1 { + return fs.ErrNotExist + } + + if len(parts) == 1 { + d.Lock() + if perm&fs.ModeDir == 0 { + perm |= fs.ModeDir + } + _, ok := d.dirs[parts[0]] + if !ok { + d.dirs[parts[0]] = &dir{ + info: fileinfo{ + name: parts[0], + size: 0x100, + modified: time.Now(), + mode: perm, + }, + dirs: map[string]*dir{}, + files: map[string]*file{}, + } + } + d.Unlock() + if ok { + return fs.ErrExist + } + + return nil + } + + d.RLock() + defer d.RUnlock() + return d.dirs[parts[0]].Mkdir(strings.Join(parts[1:], separator), perm) +} + func (d *dir) WriteFile(path string, data []byte, perm fs.FileMode) error { parts := strings.Split(path, separator) diff --git a/fs.go b/fs.go index cf77840..4be20f8 100644 --- a/fs.go +++ b/fs.go @@ -89,6 +89,13 @@ func (m *FS) MkdirAll(path string, perm fs.FileMode) error { return m.dir.MkdirAll(cleanse(path), perm) } +// Mkdir create a directory named path. +// Parent attributes are preserved. +// If path is already exists, returns an error. +func (m *FS) Mkdir(path string, perm fs.FileMode) error { + return m.dir.Mkdir(cleanse(path), perm) +} + // ReadFile reads the named file and returns its contents. // A successful call returns a nil error, not io.EOF. // (Because ReadFile reads the whole file, the expected EOF diff --git a/fs_test.go b/fs_test.go index 7a56979..e7fdf29 100644 --- a/fs_test.go +++ b/fs_test.go @@ -525,6 +525,84 @@ func Test_MkdirAllRoot(t *testing.T) { require.NoError(t, err) } +func Test_Mkdir(t *testing.T) { + memfs := New() + rootStat, err := memfs.Stat("/") + require.NoError(t, err) + + rootMode := rootStat.Mode() + rootModTime := rootStat.ModTime() + + t.Run("create directory on root", func(t *testing.T) { + + err = memfs.Mkdir("/dir", 0o444) + assert.NoError(t, err) + + stat, err := memfs.Stat("/dir") + assert.NoError(t, err) + assert.Equal(t, 0o444|fs.ModeDir, stat.Mode()) + + rootStat, err := memfs.Stat("/") + assert.NoError(t, err) + assert.Equal(t, rootMode, rootStat.Mode()) + assert.Equal(t, rootModTime, rootStat.ModTime()) + }) + + t.Run("create directory on sub directory", func(t *testing.T) { + err := memfs.Mkdir("/sub", 0o444) + assert.NoError(t, err) + + subStat, err := memfs.Stat("/sub") + require.NoError(t, err) + subMode := subStat.Mode() + subModTime := subStat.ModTime() + + err = memfs.Mkdir("/sub/dir", 0o666) + assert.NoError(t, err) + + stat, err := memfs.Stat("/sub/dir") + assert.NoError(t, err) + assert.Equal(t, 0o666|fs.ModeDir, stat.Mode()) + + subStat, err = memfs.Stat("/sub") + assert.NoError(t, err) + assert.Equal(t, subMode, subStat.Mode()) + assert.Equal(t, subModTime, subStat.ModTime()) + + rootStat, err := memfs.Stat("/") + assert.NoError(t, err) + assert.Equal(t, rootMode, rootStat.Mode()) + assert.Equal(t, rootModTime, rootStat.ModTime()) + }) + + t.Run("file already exists", func(t *testing.T) { + err := memfs.WriteFile("/file", []byte{}, 0o777) + assert.NoError(t, err) + + err = memfs.Mkdir("/file", 0o644) + assert.Error(t, err) + + err = memfs.Mkdir("/file/dir", 0o644) + assert.Error(t, err) + }) + + t.Run("directory already exists", func(t *testing.T) { + err := memfs.Mkdir("/", 0o644) + assert.Error(t, err) + + err = memfs.Mkdir("/exists", 0o444) + assert.NoError(t, err) + + err = memfs.Mkdir("/exists", 0o444) + assert.Error(t, err) + }) + + t.Run("parent not exists", func(t *testing.T) { + err := memfs.Mkdir("/not_exists/dir", 0o644) + assert.Error(t, err) + }) +} + type entry struct { path string info fs.DirEntry