Skip to content

Commit d346b51

Browse files
committed
feat(utils): add resolvepath function (#294)
1 parent 9167295 commit d346b51

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"nolintlint",
4747
"onecontext",
4848
"prealloc",
49+
"rabbitweed",
4950
"repath",
5051
"Resumer",
5152
"rxgo",
@@ -63,6 +64,7 @@
6364
"usao",
6465
"varcheck",
6566
"xname",
67+
"xpander",
6668
"Zemlya",
6769
"Zeroable"
6870
]

xfs/utils/resolve-path.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package utils
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
)
7+
8+
// AbsFunc signature of function used to obtain the absolute representation of
9+
// a path.
10+
type AbsFunc func(path string) (string, error)
11+
12+
// Abs function invoker, allows a function to be used in place where
13+
// an instance of an interface would be expected.
14+
func (f AbsFunc) Abs(path string) (string, error) {
15+
return f(path)
16+
}
17+
18+
// HomeUserFunc signature of function used to obtain the user's home directory.
19+
type HomeUserFunc func() string
20+
21+
// Home function invoker, allows a function to be used in place where
22+
// an instance of an interface would be expected.
23+
func (f HomeUserFunc) Home() string {
24+
return f()
25+
}
26+
27+
// ResolveOverrides, used to override the internal functions used
28+
// to resolve the home path (os.UserHomeDir) and the abs path
29+
// (filepath.Abs). In normal usage, these do not need to be overridden,
30+
// just used for testing purposes.
31+
type ResolveOverrides struct {
32+
HomeFunc HomeUserFunc
33+
AbsFunc AbsFunc
34+
}
35+
36+
// ResolvePath performs 2 forms of path resolution. The first is resolving a
37+
// home path reference, via the ~ character; ~ is replaced by the user's
38+
// home path. The second resolves ./ or ../ relative path. The overrides
39+
// does not need to be provided
40+
func ResolvePath(path string, overrides ...ResolveOverrides) string {
41+
result := path
42+
43+
if len(overrides) > 0 {
44+
override := overrides[0]
45+
if result[0] == '~' {
46+
result = filepath.Join(override.HomeFunc(), result[1:])
47+
} else {
48+
a, err := override.AbsFunc(result)
49+
50+
if err == nil {
51+
result = a
52+
}
53+
}
54+
} else {
55+
if result[0] == '~' {
56+
if h, err := os.UserHomeDir(); err == nil {
57+
result = filepath.Join(h, result[1:])
58+
}
59+
} else {
60+
if a, err := filepath.Abs(result); err == nil {
61+
result = a
62+
}
63+
}
64+
}
65+
66+
return result
67+
}

xfs/utils/resolve-path_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package utils_test
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
"strings"
7+
8+
. "github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega"
10+
11+
"github.com/snivilised/extendio/xfs/utils"
12+
)
13+
14+
type RPEntry struct {
15+
given string
16+
should string
17+
path string
18+
expect string
19+
}
20+
21+
var fakeHome = filepath.Join(string(filepath.Separator), "home", "rabbitweed")
22+
var fakeAbs = filepath.Join(string(filepath.Separator), "home", "rabbitweed", "music", "xpander")
23+
var fakeUpAbs = filepath.Join(string(filepath.Separator), "home", "rabbitweed", "music")
24+
25+
func fakeHomeResolver() string {
26+
return fakeHome
27+
}
28+
29+
func fakeAbsResolver(path string) (string, error) {
30+
if strings.HasPrefix(path, "..") {
31+
return filepath.Join(fakeUpAbs, path[2:]), nil
32+
}
33+
34+
if strings.HasPrefix(path, ".") {
35+
return filepath.Join(fakeAbs, path[1:]), nil
36+
}
37+
38+
return path, nil
39+
}
40+
41+
var _ = Describe("ResolvePath", func() {
42+
DescribeTable("Overrides provided",
43+
func(entry *RPEntry) {
44+
overrides := utils.ResolveOverrides{
45+
HomeFunc: fakeHomeResolver,
46+
AbsFunc: fakeAbsResolver,
47+
}
48+
49+
if filepath.Separator == '/' {
50+
actual := utils.ResolvePath(entry.path, overrides)
51+
Expect(actual).To(Equal(entry.expect))
52+
} else {
53+
normalisedPath := strings.ReplaceAll(entry.path, "/", string(filepath.Separator))
54+
normalisedExpect := strings.ReplaceAll(entry.expect, "/", string(filepath.Separator))
55+
56+
actual := utils.ResolvePath(normalisedPath, overrides)
57+
Expect(actual).To(Equal(normalisedExpect))
58+
}
59+
},
60+
func(entry *RPEntry) string {
61+
return fmt.Sprintf("🧪 ===> given: '%v', should: '%v'", entry.given, entry.should)
62+
},
63+
64+
Entry(nil, &RPEntry{
65+
given: "path is a valid absolute path",
66+
should: "return path unmodified",
67+
path: "/home/rabbitweed/foo",
68+
expect: "/home/rabbitweed/foo",
69+
}),
70+
Entry(nil, &RPEntry{
71+
given: "path contains leading ~",
72+
should: "replace ~ with home path",
73+
path: "~/foo",
74+
expect: "/home/rabbitweed/foo",
75+
}),
76+
Entry(nil, &RPEntry{
77+
given: "path is relative to cwd",
78+
should: "replace ~ with home path",
79+
path: "./foo",
80+
expect: "/home/rabbitweed/music/xpander/foo",
81+
}),
82+
Entry(nil, &RPEntry{
83+
given: "path is relative to parent",
84+
should: "replace ~ with home path",
85+
path: "../foo",
86+
expect: "/home/rabbitweed/music/foo",
87+
}),
88+
)
89+
90+
When("No overrides provided", func() {
91+
Context("and: home", func() {
92+
It("🧪 should: not fail", func() {
93+
utils.ResolvePath("~/")
94+
})
95+
})
96+
97+
Context("and: abs", func() {
98+
It("🧪 should: not fail", func() {
99+
utils.ResolvePath("./")
100+
})
101+
})
102+
})
103+
})

0 commit comments

Comments
 (0)