diff --git a/framework/lib/path.ts b/framework/lib/path.ts new file mode 100644 index 0000000000000000000000000000000000000000..9eddcb9d66837ef10a3d6c738bb1650ac96d8aca --- /dev/null +++ b/framework/lib/path.ts @@ -0,0 +1,71 @@ +const unique = new Map>(); +const final = new FinalizationRegistry((raw: string) => unique.delete(raw)); + +export class Path { + raw: string; + + static from(...parts: (string | Path)[]) { + return new Path(...parts.map((x) => x instanceof Path ? x.raw : x)); + } + + /** For convenience, paths are deduplicated. */ + constructor(...parts: string[]) { + const raw = this.raw = + parts.length === 1 && parts[0] && path.isAbsolute(parts[0]) + ? parts[0] + : path.resolve(...parts); + const existing = unique.get(raw)?.deref(); + if (existing) return existing; + unique.set(raw, new WeakRef(this)); + final.register(this, raw); + } + + toString() { + return this.raw; + } + + get parent(): Path | null { + const { raw } = this; + const parent = path.dirname(raw); + if (raw === parent) return null; + return new Path(parent); + } + + get relative() { + return path.relative(".", this.raw); + } + + get ext() { + return path.extname(this.raw); + } + + get base() { + return path.basename(this.raw); + } + + get baseWithoutExt() { + return path.basename(this.raw, this.ext); + } + + join(subPath: string, ...more: string[]) { + return new Path(this.raw, subPath, ...more); + } + + existsSync() { + return fs.existsSync(this.raw); + } + + static sep = path.sep; + + [Symbol.for("nodejs.util.inspect.custom")]( + _: number, + o: util.InspectOptionsStylized, + i: typeof util.inspect, + ) { + return o.stylize(`Path ${i(this.raw, false, 1, false)}`, "special"); + } +} + +import * as path from "node:path"; +import * as fs from "./fs.ts"; +import * as util from "node:util";