authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-09-04 23:43:19-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-10-14 02:40:47-07:00
logfc2505c256e9e0b1daa77481cc6ca104cf5a3a5d
treeba573a439250b4b7dd442dc62a1b67d433ed8d8e
parent72c169e375b5d91b36efcaf268206aeac9339a8c
signature Commit is signed but in an unrecognized format.

lib: add path library

i know this structure has been a success since i tried to reach for it at my workplace. the version in this commit is missing a lot of fs integration, but i imagine at the end of it, the fs overlay module will probably be deleted. closes #23

1 files changed, 71 insertions(+), 0 deletions(-)

framework/lib/path.ts created+71
......@@ -0,0 +1,71 @@
1const unique = new Map<string, WeakRef<Path>>();
2const final = new FinalizationRegistry((raw: string) => unique.delete(raw));
3
4export class Path {
5 raw: string;
6
7 static from(...parts: (string | Path)[]) {
8 return new Path(...parts.map((x) => x instanceof Path ? x.raw : x));
9 }
10
11 /** For convenience, paths are deduplicated. */
12 constructor(...parts: string[]) {
13 const raw = this.raw =
14 parts.length === 1 && parts[0] && path.isAbsolute(parts[0])
15 ? parts[0]
16 : path.resolve(...parts);
17 const existing = unique.get(raw)?.deref();
18 if (existing) return existing;
19 unique.set(raw, new WeakRef(this));
20 final.register(this, raw);
21 }
22
23 toString() {
24 return this.raw;
25 }
26
27 get parent(): Path | null {
28 const { raw } = this;
29 const parent = path.dirname(raw);
30 if (raw === parent) return null;
31 return new Path(parent);
32 }
33
34 get relative() {
35 return path.relative(".", this.raw);
36 }
37
38 get ext() {
39 return path.extname(this.raw);
40 }
41
42 get base() {
43 return path.basename(this.raw);
44 }
45
46 get baseWithoutExt() {
47 return path.basename(this.raw, this.ext);
48 }
49
50 join(subPath: string, ...more: string[]) {
51 return new Path(this.raw, subPath, ...more);
52 }
53
54 existsSync() {
55 return fs.existsSync(this.raw);
56 }
57
58 static sep = path.sep;
59
60 [Symbol.for("nodejs.util.inspect.custom")](
61 _: number,
62 o: util.InspectOptionsStylized,
63 i: typeof util.inspect,
64 ) {
65 return o.stylize(`Path ${i(this.raw, false, 1, false)}`, "special");
66 }
67}
68
69import * as path from "node:path";
70import * as fs from "./fs.ts";
71import * as util from "node:util";