Skip to content

Commit 900e976

Browse files
authored
feat(stdlib): Add Path module for working with system paths (#1452)
1 parent b3dc85c commit 900e976

File tree

4 files changed

+1786
-0
lines changed

4 files changed

+1786
-0
lines changed

compiler/test/stdlib/path.test.gr

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import Path from "path"
2+
import Result from "result"
3+
import Option from "option"
4+
import List from "list"
5+
6+
let fs = Path.fromString
7+
8+
// test that a variety of properties hold for valid relative/absolute paths
9+
record ParseFileTestData {
10+
pathStr: String,
11+
expParent: String,
12+
expStr: String,
13+
expName: Option<String>,
14+
expStem: String,
15+
expExt: String,
16+
}
17+
18+
let parseFileTests = [
19+
{
20+
pathStr: "./dir/file.txt",
21+
expParent: "./dir/",
22+
expStr: "./dir/file.txt",
23+
expName: Some("file.txt"),
24+
expStem: "file",
25+
expExt: ".txt",
26+
},
27+
{
28+
pathStr: "f",
29+
expParent: ".",
30+
expStr: "./f",
31+
expName: Some("f"),
32+
expStem: "f",
33+
expExt: "",
34+
},
35+
{
36+
pathStr: "a.tar.gz",
37+
expParent: ".",
38+
expStr: "./a.tar.gz",
39+
expName: Some("a.tar.gz"),
40+
expStem: "a",
41+
expExt: ".tar.gz",
42+
},
43+
{
44+
pathStr: ".gulprc.babel.js",
45+
expParent: ".",
46+
expStr: "./.gulprc.babel.js",
47+
expName: Some(".gulprc.babel.js"),
48+
expStem: ".gulprc",
49+
expExt: ".babel.js",
50+
},
51+
{
52+
pathStr: "../../file",
53+
expParent: "../..",
54+
expStr: "../../file",
55+
expName: Some("file"),
56+
expStem: "file",
57+
expExt: "",
58+
},
59+
{
60+
pathStr: ".\\dir\\file.txt",
61+
expParent: ".",
62+
expStr: "./.\\dir\\file.txt",
63+
expName: Some(".\\dir\\file.txt"),
64+
expStem: ".\\dir\\file",
65+
expExt: ".txt",
66+
},
67+
{
68+
pathStr: "/dir/file.txt",
69+
expParent: "/dir/",
70+
expStr: "/dir/file.txt",
71+
expName: Some("file.txt"),
72+
expStem: "file",
73+
expExt: ".txt",
74+
},
75+
{
76+
pathStr: "/dir/../file",
77+
expParent: "/",
78+
expStr: "/file",
79+
expName: Some("file"),
80+
expStem: "file",
81+
expExt: "",
82+
},
83+
{
84+
pathStr: "C:/Users/me.txt",
85+
expParent: "C:/Users/",
86+
expStr: "C:/Users/me.txt",
87+
expName: Some("me.txt"),
88+
expStem: "me",
89+
expExt: ".txt",
90+
},
91+
]
92+
93+
List.forEach(({ pathStr, expParent, expStr, expName, expStem, expExt }) => {
94+
let path = fs(pathStr)
95+
assert Path.toString(path) == expStr
96+
assert fs(expParent) == Path.parent(path)
97+
assert expName == Path.basename(path)
98+
assert Ok(expStem) == Path.stem(path)
99+
assert Ok(expExt) == Path.extension(path)
100+
}, parseFileTests)
101+
102+
record ParseDirTestData {
103+
pathStr: String,
104+
expParent: String,
105+
expStr: String,
106+
expName: Option<String>,
107+
}
108+
109+
let parseDirTests = [
110+
{ pathStr: "dir/../../", expParent: "../..", expStr: "../", expName: None },
111+
{
112+
pathStr: ".git/",
113+
expParent: ".",
114+
expStr: "./.git/",
115+
expName: Some(".git"),
116+
},
117+
{ pathStr: ".", expParent: "..", expStr: "./", expName: None },
118+
{ pathStr: ".////", expParent: "..", expStr: "./", expName: None },
119+
{ pathStr: "../", expParent: "../..", expStr: "../", expName: None },
120+
{ pathStr: "", expParent: "..", expStr: "./", expName: None },
121+
{ pathStr: "/../..", expParent: "/", expStr: "/", expName: None },
122+
{ pathStr: "/", expParent: "/", expStr: "/", expName: None },
123+
{
124+
pathStr: "/bin/dir/..",
125+
expParent: "/",
126+
expStr: "/bin/",
127+
expName: Some("bin"),
128+
},
129+
{ pathStr: "C:/", expParent: "C:/", expStr: "C:/", expName: None },
130+
{ pathStr: "c:/.././..", expParent: "c:/", expStr: "c:/", expName: None },
131+
]
132+
133+
List.forEach(({ pathStr, expParent, expStr, expName }: ParseDirTestData) => {
134+
let path = fs(pathStr)
135+
assert Path.toString(path) == expStr
136+
assert fs(expParent) == Path.parent(path)
137+
assert expName == Path.basename(path)
138+
}, parseDirTests)
139+
140+
// miscellaneous parsing tests
141+
assert fs("") == fs(".")
142+
assert fs(".") == fs("./")
143+
assert fs("dir") != fs("dir/")
144+
assert Path.toPlatformString(fs("C:/Users/me/"), Path.Windows) ==
145+
"C:\\Users\\me\\"
146+
assert Path.toPlatformString(
147+
Path.fromPlatformString("C:\\Users/me\\", Path.Windows),
148+
Path.Windows
149+
) ==
150+
"C:\\Users\\me\\"
151+
assert Path.toPlatformString(
152+
Path.fromPlatformString(".\\dir/me\\", Path.Windows),
153+
Path.Posix
154+
) ==
155+
"./dir/me/"
156+
157+
assert Path.stem(fs("dir/")) == Err(Path.IncompatiblePathType)
158+
assert Path.extension(fs("dir/")) == Err(Path.IncompatiblePathType)
159+
160+
// tests properties about the types of paths
161+
162+
record PathTypeTest {
163+
pathStr: String,
164+
isDir: Bool,
165+
isAbs: Bool,
166+
}
167+
168+
let pathTypeTests = [
169+
{ pathStr: "/", isDir: true, isAbs: true },
170+
{ pathStr: "/file", isDir: false, isAbs: true },
171+
{ pathStr: ".", isDir: true, isAbs: false },
172+
{ pathStr: "./file", isDir: false, isAbs: false },
173+
]
174+
175+
List.forEach(({ pathStr, isDir, isAbs }) => {
176+
let path = fs(pathStr)
177+
assert isDir == Path.isDirectory(path)
178+
assert isAbs == Path.isAbsolute(path)
179+
}, pathTypeTests)
180+
181+
assert Path.root(fs("/dir/")) == Ok(Path.Root)
182+
assert Path.root(fs("C:/dir/")) == Ok(Path.Drive('C'))
183+
assert Path.root(fs("c:/dir/")) == Ok(Path.Drive('c'))
184+
assert Path.root(fs("file")) == Err(Path.IncompatiblePathType)
185+
186+
record AppendTestData {
187+
base: String,
188+
toAppend: String,
189+
final: String,
190+
}
191+
192+
// test appending onto relative and absolute paths
193+
let appendTests = [
194+
{ base: "dir/", toAppend: "inner", final: "dir/inner" },
195+
{ base: "dir /", toAppend: " inner", final: "dir / inner" },
196+
{ base: "./dir/", toAppend: "../../other", final: "../other" },
197+
{ base: ".", toAppend: ".././../", final: "../.." },
198+
{ base: "dir/", toAppend: "f.txt", final: "dir/f.txt" },
199+
{ base: "./dir/", toAppend: "../../script", final: "../script" },
200+
{ base: ".", toAppend: ".././../file", final: "../../file" },
201+
{ base: "/usr/", toAppend: "./bin/", final: "/usr/bin/" },
202+
{ base: "/usr/", toAppend: "../bin/", final: "/bin/" },
203+
{ base: "/", toAppend: "../..", final: "/" },
204+
{ base: "C:/", toAppend: "../..", final: "C:/" },
205+
{ base: "/etc/", toAppend: "./f.txt", final: "/etc/f.txt" },
206+
{ base: "/usr/", toAppend: "../../file", final: "/file" },
207+
]
208+
209+
List.forEach(({ base, toAppend, final }) => {
210+
let path = fs(base)
211+
let expPath = fs(final)
212+
let append = Path.append(path, fs(toAppend))
213+
assert append == Ok(expPath)
214+
}, appendTests)
215+
216+
Path.append(fs("file"), fs("f")) == Err(Path.AppendToFile)
217+
Path.append(fs("/d/"), fs("/f")) == Err(Path.AppendAbsolute)
218+
219+
record RelativeToDirTestData {
220+
source: String,
221+
dest: String,
222+
result: Result<Path.Path, Path.RelativizationError>,
223+
}
224+
225+
// test the relativeTo function
226+
let valid = path => Ok(fs(path))
227+
let relativeToTests = [
228+
{ source: "./dir", dest: ".", result: valid("..") },
229+
{ source: ".", dest: ".", result: valid(".") },
230+
{ source: "..", dest: ".", result: Err(Path.ImpossibleRelativization) },
231+
{ source: "/", dest: "/", result: valid(".") },
232+
{ source: "/usr/bin", dest: "/usr/bin", result: valid("../bin") },
233+
{
234+
source: "/bin",
235+
dest: "C:/Users",
236+
result: Err(Path.Incompatible(Path.DifferentRoots)),
237+
},
238+
{ source: "./dir", dest: "./dir/inner", result: valid("./inner") },
239+
{ source: "..", dest: "../../other", result: valid("../other") },
240+
{ source: "..", dest: "./f", result: Err(Path.ImpossibleRelativization) },
241+
{ source: "/bin", dest: "/bin/f", result: valid("./f") },
242+
{ source: "/a/b", dest: "/c/d.txt", result: valid("../../c/d.txt") },
243+
{
244+
source: "/bin",
245+
dest: "C:/a.txt",
246+
result: Err(Path.Incompatible(Path.DifferentRoots)),
247+
},
248+
{
249+
source: "/bin",
250+
dest: "./a.txt",
251+
result: Err(Path.Incompatible(Path.DifferentBases)),
252+
},
253+
]
254+
255+
List.forEach(({ source, dest, result }) => {
256+
let source = fs(source)
257+
let dest = fs(dest)
258+
assert Path.relativeTo(source, dest) == result
259+
}, relativeToTests)
260+
261+
// ancestry tests
262+
assert Path.ancestry(fs("/usr"), fs("/usr/bin/../sbin")) == Ok(Path.Descendant)
263+
assert Path.ancestry(fs("dir"), fs("dir/inner/file.txt")) == Ok(Path.Descendant)
264+
assert Path.ancestry(fs("/usr/bin"), fs("/usr")) == Ok(Path.Ancestor)
265+
assert Path.ancestry(fs("/usr"), fs("/usr")) == Ok(Path.Self)
266+
assert Path.ancestry(fs("/usr/"), fs("/usr")) == Ok(Path.Self)
267+
assert Path.ancestry(fs("/usr"), fs("/etc")) == Ok(Path.NoLineage)
268+
assert Path.ancestry(fs("../dir1"), fs("./dir2")) == Ok(Path.NoLineage)
269+
assert Path.ancestry(fs("./dir1"), fs("../../dir2")) == Ok(Path.NoLineage)
270+
assert Path.ancestry(fs("./dir1"), fs("/dir2")) == Err(Path.DifferentBases)
271+
assert Path.ancestry(fs("C:/dir1"), fs("/dir2")) == Err(Path.DifferentRoots)

compiler/test/suites/stdlib.re

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ describe("stdlib", ({test, testSkip}) => {
104104
assertStdlib("marshal.test");
105105
assertStdlib("number.test");
106106
assertStdlib("option.test");
107+
assertStdlib("path.test");
107108
assertStdlib("pervasives.test");
108109
assertStdlib("queue.test");
109110
assertStdlib("range.test");

0 commit comments

Comments
 (0)