Skip to content

Commit

Permalink
feat: add fsutil package
Browse files Browse the repository at this point in the history
  • Loading branch information
twelvelabs committed Jun 10, 2023
1 parent af45bc7 commit e7723cd
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 0 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"Cygwin",
"dasherize",
"flect",
"fsutil",
"gobuffalo",
"golangci",
"GOPROXY",
Expand Down
86 changes: 86 additions & 0 deletions fsutil/fsutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package fsutil

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
)

const (
// DefaultDirMode grants `rwx------`.
DefaultDirMode = 0700
// DefaultFileMode grants `rw-------`.
DefaultFileMode = 0600
)

var (
osUserHomeDir = os.UserHomeDir
filepathAbs = filepath.Abs
osMkdirAll = os.MkdirAll
osWriteFile = os.WriteFile
)

func NoPathExists(path string) bool {
_, err := os.Stat(path)
// for some reason os.ErrInvalid sometimes != syscall.EINVAL :shrug:
if errors.Is(err, os.ErrNotExist) ||
errors.Is(err, os.ErrInvalid) ||
errors.Is(err, syscall.EINVAL) {
return true
}
return false
}

func PathExists(path string) bool {
return !NoPathExists(path)
}

// NormalizePath ensures that name is an absolute path.
// Environment variables (and the ~ string) are expanded.
func NormalizePath(name string) (string, error) {
normalized := strings.TrimSpace(name)
if normalized == "" {
return "", nil
}

// Replace ENV vars
normalized = os.ExpandEnv(normalized)

// Replace ~
if strings.HasPrefix(normalized, "~") {
home, err := osUserHomeDir()
if err != nil {
return "", fmt.Errorf("unable to normalize %s: %w", name, err)
}
normalized = home + strings.TrimPrefix(normalized, "~")
}

// Ensure abs path
normalized, err := filepathAbs(normalized)
if err != nil {
return "", fmt.Errorf("unable to normalize %s: %w", name, err)
}

return normalized, nil
}

// EnsureDirWritable ensures that path is a writable directory.
// Will attempt to create a new directory if path does not exist.
func EnsureDirWritable(path string) error {
// Ensure dir exists (and IsDir).
err := osMkdirAll(path, DefaultDirMode)
if err != nil {
return fmt.Errorf("ensure dir: %w", err)
}

f := filepath.Join(path, ".touch")
if err := osWriteFile(f, []byte(""), DefaultFileMode); err != nil {
return fmt.Errorf("ensure writable: %w", err)
}
defer os.Remove(f)

return nil
}
149 changes: 149 additions & 0 deletions fsutil/fsutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package fsutil

import (
"errors"
"os"
"path/filepath"
"testing"

"github.com/prashantv/gostub"
"github.com/stretchr/testify/assert"

"github.com/twelvelabs/termite/testutil"
)

func TestNoPathExists(t *testing.T) {
testutil.InTempDir(t, func(dir string) {
assert.NoFileExists(t, "foo.txt")
assert.Equal(t, true, NoPathExists("foo.txt"))

testutil.WriteFile(t, "foo.txt", []byte(""), 0600)
assert.Equal(t, false, NoPathExists("foo.txt"))
})
}

func TestPathExists(t *testing.T) {
testutil.InTempDir(t, func(dir string) {
assert.NoFileExists(t, "foo.txt")
assert.Equal(t, false, PathExists("foo.txt"))

testutil.WriteFile(t, "foo.txt", []byte(""), 0600)
assert.Equal(t, true, PathExists("foo.txt"))
})
}

func TestNormalizePath(t *testing.T) {
homeDir, _ := os.UserHomeDir()
workingDir, _ := filepath.Abs(".")

tests := []struct {
Desc string
EnvVars map[string]string
Input string
Output string
Err string
}{
{
Desc: "is a noop when passed an empty string",
Input: "",
Output: "",
Err: "",
},
{
Desc: "expands env vars",
Input: filepath.Join(".", "${FOO}-dir", "$BAR"),
Output: filepath.Join(workingDir, "aaa-dir", "bbb"),
EnvVars: map[string]string{
"FOO": "aaa",
"BAR": "bbb",
},
Err: "",
},
{
Desc: "expands tilde",
Input: "~",
Output: homeDir,
Err: "",
},
{
Desc: "expands tilde when prefix",
Input: filepath.Join("~", "foo"),
Output: filepath.Join(homeDir, "foo"),
Err: "",
},
{
Desc: "returns an absolute path",
Input: ".",
Output: workingDir,
Err: "",
},
}
for _, tt := range tests {
t.Run(tt.Desc, func(t *testing.T) {
if tt.EnvVars != nil {
for k, v := range tt.EnvVars {
t.Setenv(k, v)
}
}

actual, err := NormalizePath(tt.Input)

assert.Equal(t, tt.Output, actual)
if tt.Err == "" {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, tt.Err)
}
})
}
}

func TestNormalizePath_WhenUserHomeDirError(t *testing.T) {
stubs := gostub.StubFunc(&osUserHomeDir, "", errors.New("boom"))
defer stubs.Reset()

actual, err := NormalizePath("~/foo")

assert.Error(t, err)
assert.Equal(t, "", actual)
}

func TestNormalizePath_WhenAbsError(t *testing.T) {
stubs := gostub.StubFunc(&filepathAbs, "foo", errors.New("boom"))
defer stubs.Reset()

actual, err := NormalizePath("foo")

assert.Error(t, err)
assert.Equal(t, "", actual)
}

func TestEnsureDirWritable(t *testing.T) {
testutil.InTempDir(t, func(tmpDir string) {
dir := filepath.Join(tmpDir, "foo")
err := EnsureDirWritable(dir)
assert.NoError(t, err)
assert.DirExists(t, dir, "dir should exist")

dirEntry := filepath.Join(dir, "bar")
testutil.WriteFile(t, dirEntry, []byte(""), 0600)
assert.FileExists(t, dirEntry, "dir should be writable")
})
}

func TestEnsureDirWritable_WhenMkdirAllError(t *testing.T) {
stubs := gostub.StubFunc(&osMkdirAll, errors.New("boom"))
defer stubs.Reset()

err := EnsureDirWritable("foo")
assert.Error(t, err)
}

func TestEnsureDirWritable_WhenWriteFileError(t *testing.T) {
stubs := gostub.StubFunc(&osMkdirAll, nil).
StubFunc(&osWriteFile, errors.New("boom"))
defer stubs.Reset()

err := EnsureDirWritable("foo")
assert.Error(t, err)
}

0 comments on commit e7723cd

Please sign in to comment.