Skip to content

Commit e7723cd

Browse files
committed
feat: add fsutil package
1 parent af45bc7 commit e7723cd

File tree

3 files changed

+236
-0
lines changed

3 files changed

+236
-0
lines changed

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"Cygwin",
1212
"dasherize",
1313
"flect",
14+
"fsutil",
1415
"gobuffalo",
1516
"golangci",
1617
"GOPROXY",

fsutil/fsutil.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package fsutil
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"syscall"
10+
)
11+
12+
const (
13+
// DefaultDirMode grants `rwx------`.
14+
DefaultDirMode = 0700
15+
// DefaultFileMode grants `rw-------`.
16+
DefaultFileMode = 0600
17+
)
18+
19+
var (
20+
osUserHomeDir = os.UserHomeDir
21+
filepathAbs = filepath.Abs
22+
osMkdirAll = os.MkdirAll
23+
osWriteFile = os.WriteFile
24+
)
25+
26+
func NoPathExists(path string) bool {
27+
_, err := os.Stat(path)
28+
// for some reason os.ErrInvalid sometimes != syscall.EINVAL :shrug:
29+
if errors.Is(err, os.ErrNotExist) ||
30+
errors.Is(err, os.ErrInvalid) ||
31+
errors.Is(err, syscall.EINVAL) {
32+
return true
33+
}
34+
return false
35+
}
36+
37+
func PathExists(path string) bool {
38+
return !NoPathExists(path)
39+
}
40+
41+
// NormalizePath ensures that name is an absolute path.
42+
// Environment variables (and the ~ string) are expanded.
43+
func NormalizePath(name string) (string, error) {
44+
normalized := strings.TrimSpace(name)
45+
if normalized == "" {
46+
return "", nil
47+
}
48+
49+
// Replace ENV vars
50+
normalized = os.ExpandEnv(normalized)
51+
52+
// Replace ~
53+
if strings.HasPrefix(normalized, "~") {
54+
home, err := osUserHomeDir()
55+
if err != nil {
56+
return "", fmt.Errorf("unable to normalize %s: %w", name, err)
57+
}
58+
normalized = home + strings.TrimPrefix(normalized, "~")
59+
}
60+
61+
// Ensure abs path
62+
normalized, err := filepathAbs(normalized)
63+
if err != nil {
64+
return "", fmt.Errorf("unable to normalize %s: %w", name, err)
65+
}
66+
67+
return normalized, nil
68+
}
69+
70+
// EnsureDirWritable ensures that path is a writable directory.
71+
// Will attempt to create a new directory if path does not exist.
72+
func EnsureDirWritable(path string) error {
73+
// Ensure dir exists (and IsDir).
74+
err := osMkdirAll(path, DefaultDirMode)
75+
if err != nil {
76+
return fmt.Errorf("ensure dir: %w", err)
77+
}
78+
79+
f := filepath.Join(path, ".touch")
80+
if err := osWriteFile(f, []byte(""), DefaultFileMode); err != nil {
81+
return fmt.Errorf("ensure writable: %w", err)
82+
}
83+
defer os.Remove(f)
84+
85+
return nil
86+
}

fsutil/fsutil_test.go

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package fsutil
2+
3+
import (
4+
"errors"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/prashantv/gostub"
10+
"github.com/stretchr/testify/assert"
11+
12+
"github.com/twelvelabs/termite/testutil"
13+
)
14+
15+
func TestNoPathExists(t *testing.T) {
16+
testutil.InTempDir(t, func(dir string) {
17+
assert.NoFileExists(t, "foo.txt")
18+
assert.Equal(t, true, NoPathExists("foo.txt"))
19+
20+
testutil.WriteFile(t, "foo.txt", []byte(""), 0600)
21+
assert.Equal(t, false, NoPathExists("foo.txt"))
22+
})
23+
}
24+
25+
func TestPathExists(t *testing.T) {
26+
testutil.InTempDir(t, func(dir string) {
27+
assert.NoFileExists(t, "foo.txt")
28+
assert.Equal(t, false, PathExists("foo.txt"))
29+
30+
testutil.WriteFile(t, "foo.txt", []byte(""), 0600)
31+
assert.Equal(t, true, PathExists("foo.txt"))
32+
})
33+
}
34+
35+
func TestNormalizePath(t *testing.T) {
36+
homeDir, _ := os.UserHomeDir()
37+
workingDir, _ := filepath.Abs(".")
38+
39+
tests := []struct {
40+
Desc string
41+
EnvVars map[string]string
42+
Input string
43+
Output string
44+
Err string
45+
}{
46+
{
47+
Desc: "is a noop when passed an empty string",
48+
Input: "",
49+
Output: "",
50+
Err: "",
51+
},
52+
{
53+
Desc: "expands env vars",
54+
Input: filepath.Join(".", "${FOO}-dir", "$BAR"),
55+
Output: filepath.Join(workingDir, "aaa-dir", "bbb"),
56+
EnvVars: map[string]string{
57+
"FOO": "aaa",
58+
"BAR": "bbb",
59+
},
60+
Err: "",
61+
},
62+
{
63+
Desc: "expands tilde",
64+
Input: "~",
65+
Output: homeDir,
66+
Err: "",
67+
},
68+
{
69+
Desc: "expands tilde when prefix",
70+
Input: filepath.Join("~", "foo"),
71+
Output: filepath.Join(homeDir, "foo"),
72+
Err: "",
73+
},
74+
{
75+
Desc: "returns an absolute path",
76+
Input: ".",
77+
Output: workingDir,
78+
Err: "",
79+
},
80+
}
81+
for _, tt := range tests {
82+
t.Run(tt.Desc, func(t *testing.T) {
83+
if tt.EnvVars != nil {
84+
for k, v := range tt.EnvVars {
85+
t.Setenv(k, v)
86+
}
87+
}
88+
89+
actual, err := NormalizePath(tt.Input)
90+
91+
assert.Equal(t, tt.Output, actual)
92+
if tt.Err == "" {
93+
assert.NoError(t, err)
94+
} else {
95+
assert.ErrorContains(t, err, tt.Err)
96+
}
97+
})
98+
}
99+
}
100+
101+
func TestNormalizePath_WhenUserHomeDirError(t *testing.T) {
102+
stubs := gostub.StubFunc(&osUserHomeDir, "", errors.New("boom"))
103+
defer stubs.Reset()
104+
105+
actual, err := NormalizePath("~/foo")
106+
107+
assert.Error(t, err)
108+
assert.Equal(t, "", actual)
109+
}
110+
111+
func TestNormalizePath_WhenAbsError(t *testing.T) {
112+
stubs := gostub.StubFunc(&filepathAbs, "foo", errors.New("boom"))
113+
defer stubs.Reset()
114+
115+
actual, err := NormalizePath("foo")
116+
117+
assert.Error(t, err)
118+
assert.Equal(t, "", actual)
119+
}
120+
121+
func TestEnsureDirWritable(t *testing.T) {
122+
testutil.InTempDir(t, func(tmpDir string) {
123+
dir := filepath.Join(tmpDir, "foo")
124+
err := EnsureDirWritable(dir)
125+
assert.NoError(t, err)
126+
assert.DirExists(t, dir, "dir should exist")
127+
128+
dirEntry := filepath.Join(dir, "bar")
129+
testutil.WriteFile(t, dirEntry, []byte(""), 0600)
130+
assert.FileExists(t, dirEntry, "dir should be writable")
131+
})
132+
}
133+
134+
func TestEnsureDirWritable_WhenMkdirAllError(t *testing.T) {
135+
stubs := gostub.StubFunc(&osMkdirAll, errors.New("boom"))
136+
defer stubs.Reset()
137+
138+
err := EnsureDirWritable("foo")
139+
assert.Error(t, err)
140+
}
141+
142+
func TestEnsureDirWritable_WhenWriteFileError(t *testing.T) {
143+
stubs := gostub.StubFunc(&osMkdirAll, nil).
144+
StubFunc(&osWriteFile, errors.New("boom"))
145+
defer stubs.Reset()
146+
147+
err := EnsureDirWritable("foo")
148+
assert.Error(t, err)
149+
}

0 commit comments

Comments
 (0)