ludic/examples/library/fs.ludic
Orkuncakilkaya dd4f5a26ad
All checks were successful
docs / build-and-deploy (push) Successful in 2s
feat(stdlib): add Fs.* / Path.* / Mime.* — filesystem, paths, content types (#10)
A cohesive filesystem & IO library — the foundation for saves, config, mods, and
asset loading — wrapping the bare file_* builtins into one safe, ergonomic API a
non-expert can use without touching a file descriptor or a byte buffer.

  Path.*  join / dir / base / ext / stem / normalize   (pure lexical string ops)
  Fs.*    exists / is_dir / read_text / write_text / append_text / remove /
          size / mkdir / copy / list
  Mime.*  of (extension table) / sniff (magic bytes: PNG/JPEG/GIF/PDF)

Pure string IR for Path.*; libc (fopen/access/mkdir/rename/opendir…) for Fs.*;
C-free, emitted on demand (g_uses_fsrt). Safety and determinism baked in:
- write_text and copy are atomic (write a temp file, then rename over the target)
  so a crash mid-write never corrupts the previous file;
- mkdir creates parents (mkdir -p);
- list is sorted for a stable, reproducible directory walk;
- fallible calls return values (null / false / -1), never crashes — ready for a
  first-class try/else when the error-handling work lands.

Complements Os.* (#21): Os supplies per-user locations, Fs the operations. v1
targets the native macOS/BSD filesystem with "/" separators; Windows separators,
a sandboxed wasm virtual FS, recursive directory copy, and richer magic-byte
sniffing are documented follow-ups.

- examples/library/fs.ludic: 32 assertions across pure Path ops (incl. normalize
  resolving ./ .. and duplicate slashes), a real create/read/append/copy/list/
  remove cycle under build/, and Mime by-extension + by-magic (GIF signature vs a
  .bin extension). Wired into `x test` (now 56 passed).
- docs: new Path, Fs, and Mime sections + 18 per-symbol pages; inventory updated;
  every fence passes check-docs; site builds via docgen.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Closes #10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 22:43:50 +03:00

58 lines
2.5 KiB
Text

# fs.ludic — Fs.* / Path.* / Mime.* over a scratch directory under build/. Path
# ops are pure and deterministic; the Fs ops create, read, copy, list, and delete
# real files (cleaned up at the end so reruns stay identical); Mime maps by
# extension and by magic bytes. Running it prints: 1 .. 32 in order.
program Fs {
entry {
# ---- Path.* (pure) ----
if Path.join("mods", "foo") == "mods/foo" { print(1) }
if Path.join("mods/", "foo") == "mods/foo" { print(2) }
if Path.join("mods", "/abs") == "/abs" { print(3) }
if Path.base("mods/foo/data.json") == "data.json" { print(4) }
if Path.dir("mods/foo/data.json") == "mods/foo" { print(5) }
if Path.dir("data.json") == "." { print(6) }
if Path.ext("data.json") == ".json" { print(7) }
if Path.ext("data") == "" { print(8) }
if Path.stem("data.json") == "data" { print(9) }
if Path.normalize("a/./b/../c") == "a/c" { print(10) }
if Path.normalize("a//b/") == "a/b" { print(11) }
if Path.normalize("/a/../..") == "/" { print(12) }
if Path.normalize("./x") == "x" { print(13) }
# ---- Fs.* (real files under build/) ----
let dir = "build/fs_test_dir"
let a = "build/fs_test_dir/a.txt"
let b = "build/fs_test_dir/b.txt"
if Fs.mkdir("build/fs_test_dir/sub") { print(14) }
if Fs.is_dir(dir) { print(15) }
if Fs.write_text(a, "hello") { print(16) }
if Fs.exists(a) { print(17) }
if Fs.read_text(a) == "hello" { print(18) }
if Fs.size(a) == 5 { print(19) }
if Fs.is_dir(a) == false { print(20) }
if Fs.append_text(a, "!") { print(21) }
if Fs.read_text(a) == "hello!" { print(22) }
if Fs.copy(a, b) { print(23) }
if Fs.read_text(b) == "hello!" { print(24) }
# list is sorted and stable: a.txt, b.txt, sub
let names = Fs.list(dir)
if len(names) == 3 { print(25) }
if names[0] == "a.txt" { print(26) }
if names[2] == "sub" { print(27) }
# remove cleans up
if Fs.remove(a) and Fs.remove(b) and not Fs.exists(a) { print(28) }
# ---- Mime.* ----
if Mime.of("hero.png") == "image/png" { print(29) }
if Mime.of("save.json") == "application/json" { print(30) }
if Mime.of("weird.xyz") == "application/octet-stream" { print(31) }
# sniff detects magic bytes even when the extension lies (.bin): the GIF
# signature "GIF" is ASCII, so we can write it as ordinary text.
let magic = "build/fs_test_dir/magic.bin"
Fs.write_text(magic, "GIF89a and then some")
if Mime.sniff(magic) == "image/gif" { print(32) }
Fs.remove(magic)
}
}