char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
92a4f7e
main
avril:path
Lexical path manipulation with no filesystem access: joining, normalization,
parsing, and file: URL conversion. The algorithms follow Node's path
semantics for POSIX and Windows, including drive roots, drive-relative forms,
UNC paths, extended \\?\ / \\.\ namespaces, case-insensitive drive and
component comparison for win32.relative, and the parse/format dot and
extension rules. There is deliberately no glob support and no filesystem
lookup of any kind; realPath stays in avril:std, where symlinks and
permissions live.
import * as path from "avril:path" ; import * as posix from "avril:path/posix" ; import * as win32 from "avril:path/win32" ; path . join ( "/a" , "b" , "../c" ); // "/a/c" on POSIX hosts win32 . join ( "C:\\a" , "b" , "..\\c" ); // "C:\\a\\c" on any host posix . relative ( "/a/b/c" , "/a/d/e" ); // "../../d/e" path . toFileUrl ( "/a b/c#d" ). href ; // "file:///a%20b/c%23d" path . fromFileUrl ( new URL ( "file:///a%20b/c" )); // "/a b/c"
avril:path exports the native flavor for the target OS directly
(join, resolve, normalize, relative, dirname, basename,
extname, isAbsolute, parse, format, toNamespacedPath,
fromFileUrl, toFileUrl, sep, delimiter) plus frozen posix and
win32 namespaces with the same surface. avril:path/posix and
avril:path/win32 export the same names pinned to one flavor on any host,
so explicit Windows paths work lexically on Unix and vice versa. There is no
default export; import * as path is idiomatic. posix/win32 namespace
objects carry no nested posix/win32 fields.
Authority
Pure operations (join, normalize, dirname, basename, extname,
isAbsolute, parse, format, and the POSIX toNamespacedPath passthrough)
never touch the working directory and run under avril run -0 with no grants.
resolve, relative, and win32.toNamespacedPath (which resolves its input
first) consult the working directory only when relative, root-relative, or
drive-relative input actually needs it: inputs that are already fully qualified
need no read grant, and importing the module itself never requires any
authority. Each lookup rechecks live capability state, so a pledge()
narrowing read access is honored by the next call; there is no cached directory.
A missing grant throws the normal PermissionDenied, and
toFileUrl/fromFileUrl need no permissions at all.
Windows notes
Windows resolution never reads per-drive hidden environment entries and
needs no environment permission: when the requested drive differs from the
working directory's drive (or the directory has no drive letter, as on POSIX
hosts), resolution falls back to that drive's root instead of consulting
another drive's remembered directory. Explicit posix calls on Windows use
the POSIX view of the working directory (separators converted, drive
indicator truncated), matching Node.
Extended-namespace paths (\\?\, \\.\, and their forward-slash spellings)
work in every lexical operation but are rejected by toFileUrl and
fromFileUrl with a TypeError. UNC servers are validated strictly (control
characters, spaces, and #?%@/:[] are rejected rather than left for the URL
parser to strip or reinterpret, except inside bracketed IPv6 literals like
\\[::1]\share, which are accepted); ASCII case folds for a deterministic
representation, localhost is refused as a server (WHATWG would collapse it
and lose the share — including Unicode aliases that normalize to it, which are
rechecked on the constructed URL), and other non-ASCII names pass through IDNA
to their punycode form.
File URLs
toFileUrl(path) requires an absolute path: /-rooted for POSIX, or a
fully qualified drive (C:\…) or UNC (\\server\share\…) path for Windows.
The Windows input is normalized with the lexical win32 algorithm first, so
./.. can never cross a drive or share root (unlike a bare URL parse, which
does not know the share boundary); explicit trailing slashes are preserved.
Drive-relative (C:foo), root-only (\foo), and relative inputs throw
TypeError — as does the bare drive C:, which Node accepts but which names
no directory here — along with NUL bytes and lone surrogates. Conversion is
purely lexical and never consults the working directory. The percent-encoding
table matches Node's pathToFileURL exactly (verified differentially over the
full ASCII range, including ~, |, ^, [, ]); ordinary separators and
coherent trailing slashes survive, and ./.. normalize per URL semantics.
fromFileUrl(url) accepts a genuine URL instance or an absolute file:
URL string; forged objects and other schemes throw TypeError. Raw strings are
rejected up front for lone surrogates, NUL bytes, and tab/newline characters
(the URL parser would otherwise normalize those silently); a URL instance is
already parsed, so a surrogate it normalized to U+FFFD on construction stays
one. Non-local
POSIX hosts are rejected (localhost is local); any other Windows host
becomes a UNC path, with localhost meaning the local machine (file://server/
with no share, and drive-relative file:///C: / file:///C:foo, are rejected as
incomplete). Encoded
separators (%2F everywhere, %5C for Windows), malformed escapes, invalid
UTF-8, NUL bytes, and surrogate escapes throw instead of decoding quietly.
Unlike Node, a nonempty query or fragment is rejected rather than silently
dropped.
1# `avril:path` 2 3Lexical path manipulation with no filesystem access: joining, normalization, 4parsing, and `file:` URL conversion. The algorithms follow Node's `path` 5semantics for POSIX and Windows, including drive roots, drive-relative forms, 6UNC paths, extended `\\?\` / `\\.\` namespaces, case-insensitive drive and 7component comparison for `win32.relative`, and the `parse`/`format` dot and 8extension rules. There is deliberately no glob support and no filesystem 9lookup of any kind; `realPath` stays in `avril:std`, where symlinks and 10permissions live. 11 12``` ts 13import * as path from "avril:path"; 14import * as posix from "avril:path/posix"; 15import * as win32 from "avril:path/win32"; 16 17path.join("/a", "b", "../c"); // "/a/c" on POSIX hosts 18win32.join("C:\\a", "b", "..\\c"); // "C:\\a\\c" on any host 19posix.relative("/a/b/c", "/a/d/e"); // "../../d/e" 20 21path.toFileUrl("/a b/c#d").href; // "file:///a%20b/c%23d" 22path.fromFileUrl(new URL("file:///a%20b/c")); // "/a b/c" 23``` 24 25`avril:path` exports the native flavor for the target OS directly 26(`join`, `resolve`, `normalize`, `relative`, `dirname`, `basename`, 27`extname`, `isAbsolute`, `parse`, `format`, `toNamespacedPath`, 28`fromFileUrl`, `toFileUrl`, `sep`, `delimiter`) plus frozen `posix` and 29`win32` namespaces with the same surface. `avril:path/posix` and 30`avril:path/win32` export the same names pinned to one flavor on any host, 31so explicit Windows paths work lexically on Unix and vice versa. There is no 32default export; `import * as path` is idiomatic. `posix`/`win32` namespace 33objects carry no nested `posix`/`win32` fields. 34 35## Authority 36 37Pure operations (`join`, `normalize`, `dirname`, `basename`, `extname`, 38`isAbsolute`, `parse`, `format`, and the POSIX `toNamespacedPath` passthrough) 39never touch the working directory and run under `avril run -0` with no grants. 40`resolve`, `relative`, and `win32.toNamespacedPath` (which resolves its input 41first) consult the working directory only when relative, root-relative, or 42drive-relative input actually needs it: inputs that are already fully qualified 43need no read grant, and importing the module itself never requires any 44authority. Each lookup rechecks live capability state, so a `pledge()` 45narrowing read access is honored by the next call; there is no cached directory. 46A missing grant throws the normal `PermissionDenied`, and 47`toFileUrl`/`fromFileUrl` need no permissions at all. 48 49## Windows notes 50 51Windows resolution never reads per-drive hidden environment entries and 52needs no environment permission: when the requested drive differs from the 53working directory's drive (or the directory has no drive letter, as on POSIX 54hosts), resolution falls back to that drive's root instead of consulting 55another drive's remembered directory. Explicit `posix` calls on Windows use 56the POSIX view of the working directory (separators converted, drive 57indicator truncated), matching Node. 58 59Extended-namespace paths (`\\?\`, `\\.\`, and their forward-slash spellings) 60work in every lexical operation but are rejected by `toFileUrl` and 61`fromFileUrl` with a `TypeError`. UNC servers are validated strictly (control 62characters, spaces, and `#?%@/:[]` are rejected rather than left for the URL 63parser to strip or reinterpret, except inside bracketed IPv6 literals like 64`\\[::1]\share`, which are accepted); ASCII case folds for a deterministic 65representation, `localhost` is refused as a server (WHATWG would collapse it 66and lose the share — including Unicode aliases that normalize to it, which are 67rechecked on the constructed URL), and other non-ASCII names pass through IDNA 68to their punycode form. 69 70## File URLs 71 72`toFileUrl(path)` requires an absolute path: `/`-rooted for POSIX, or a 73fully qualified drive (`C:\…`) or UNC (`\\server\share\…`) path for Windows. 74The Windows input is normalized with the lexical `win32` algorithm first, so 75`.`/`..` can never cross a drive or share root (unlike a bare URL parse, which 76does not know the share boundary); explicit trailing slashes are preserved. 77Drive-relative (`C:foo`), root-only (`\foo`), and relative inputs throw 78`TypeError` — as does the bare drive `C:`, which Node accepts but which names 79no directory here — along with NUL bytes and lone surrogates. Conversion is 80purely lexical and never consults the working directory. The percent-encoding 81table matches Node's `pathToFileURL` exactly (verified differentially over the 82full ASCII range, including `~`, `|`, `^`, `[`, `]`); ordinary separators and 83coherent trailing slashes survive, and `.`/`..` normalize per URL semantics. 84 85`fromFileUrl(url)` accepts a genuine `URL` instance or an absolute `file:` 86URL string; forged objects and other schemes throw `TypeError`. Raw strings are 87rejected up front for lone surrogates, NUL bytes, and tab/newline characters 88(the URL parser would otherwise normalize those silently); a `URL` instance is 89already parsed, so a surrogate it normalized to U+FFFD on construction stays 90one. Non-local 91POSIX hosts are rejected (`localhost` is local); any other Windows host 92becomes a UNC path, with `localhost` meaning the local machine (`file://server/` 93with no share, and drive-relative `file:///C:` / `file:///C:foo`, are rejected as 94incomplete). Encoded 95separators (`%2F` everywhere, `%5C` for Windows), malformed escapes, invalid 96UTF-8, NUL bytes, and surrogate escapes throw instead of decoding quietly. 97Unlike Node, a nonempty query or fragment is rejected rather than silently 98dropped.