Filesystem, portable.
Note file-seq is a trap: it resolves on BOTH hosts but takes different
argument types — a java.io.File on the JVM, a string path on cljgo. A name
that resolves everywhere is not thereby portable, which is why directory
traversal lives behind this seam.
Filesystem, portable. Note `file-seq` is a trap: it resolves on BOTH hosts but takes different argument types — a java.io.File on the JVM, a string path on cljgo. A name that resolves everywhere is not thereby portable, which is why directory traversal lives behind this seam.
(delete! path)Delete the file or EMPTY directory at path. Returns nil. Not an error if
it is already absent — deleting is a statement about the end state, and both
hosts agree only if koine says so.
Delete the file or EMPTY directory at `path`. Returns nil. Not an error if it is already absent — deleting is a statement about the end state, and both hosts agree only if koine says so.
(delete-tree! path)Recursively delete path and everything under it (rm -rf). Returns nil.
Not an error if it is already absent.
No reader conditional: both hosts have a native recursive delete, and using
neither is the better trade. Deepest-first over list-tree is the same
traversal koine already guarantees, so the ORDER of removal is koine's and
cannot differ per host — and a bug here deletes the wrong thing, which is the
last place to want two implementations.
Recursively delete `path` and everything under it (`rm -rf`). Returns nil. Not an error if it is already absent. No reader conditional: both hosts have a native recursive delete, and using neither is the better trade. Deepest-first over `list-tree` is the same traversal koine already guarantees, so the ORDER of removal is koine's and cannot differ per host — and a bug here deletes the wrong thing, which is the last place to want two implementations.
(find-files root suffix)Every file under root whose path ends with suffix, SORTED.
Sorted because skill discovery must be deterministic across hosts — the underlying traversal order is not guaranteed to match.
Every file under `root` whose path ends with `suffix`, SORTED. Sorted because skill discovery must be deterministic across hosts — the underlying traversal order is not guaranteed to match.
(list-tree root)Every path under root, recursively, as strings — files and directories.
Order is unspecified per host; callers that need determinism must sort.
Every path under `root`, recursively, as strings — files and directories. Order is unspecified per host; callers that need determinism must sort.
(mkdirs! path)Create directory path and every missing parent (mkdir -p). Returns
path. Not an error if the directory already exists.
It IS an error if path exists and is not a directory — you asked for a
directory and did not get one, and the next write would go somewhere you did
not intend. The hosts disagreed here and koine picks the loud answer: cljgo
already threw, while the JVM's .mkdirs returns false and koine was
discarding it, so the call looked like it had succeeded. Found by probing the
states fs_check never entered, 2026-07-31.
Create directory `path` and every missing parent (`mkdir -p`). Returns `path`. Not an error if the directory already exists. It IS an error if `path` exists and is not a directory — you asked for a directory and did not get one, and the next write would go somewhere you did not intend. The hosts disagreed here and koine picks the loud answer: cljgo already threw, while the JVM's `.mkdirs` returns false and koine was discarding it, so the call looked like it had succeeded. Found by probing the states `fs_check` never entered, 2026-07-31.
(read-bytes path)The whole file at path as a byte array. Use this, never read-file, for
anything that is not text — the text route is lossy for non-UTF-8 bytes.
The whole file at `path` as a byte array. Use this, never `read-file`, for anything that is not text — the text route is lossy for non-UTF-8 bytes.
(read-file path)The whole file at path as a string (UTF-8).
The whole file at `path` as a string (UTF-8).
(real-path path)path with every symlink resolved, as an absolute, cleaned path. Throws if
path does not exist.
Unlike making a path absolute, this TOUCHES THE FILESYSTEM — that is the whole
point, and it is why cljg.io/absolute (Go's filepath.Abs) is not a
substitute: Abs cleans a path lexically and never follows a link, while
returning something that looks canonical.
Use it to canonicalise before comparing two paths, and to guard a directory walk against a symlink CYCLE:
(loop [[d & more] roots seen #{}]
(let [c (fs/real-path d)]
(if (contains? seen c)
(recur more seen) ; already been here — do not descend
(recur (concat more (fs/list-tree d)) (conj seen c)))))
Without it there is no cycle guard at all, and ln -s ../.. loop makes a walk
run forever — list-tree FOLLOWS directory symlinks on both hosts, verified.
Note the result may differ from the input in more than the links: macOS resolves /tmp to /private/tmp, for instance. That is canonicalisation doing its job — compare canonical paths to canonical paths, never to raw input.
A MISSING path throws on both hosts, and koine has to insist on that: cljgo
throws by itself, while the JVM's .getCanonicalPath happily cleans a path
that is not there and hands back something that LOOKS canonical. Returning
that would be the worst outcome — a caller cannot tell a resolved path from an
unresolved one, so a cycle guard would silently stop guarding.
`path` with every symlink resolved, as an absolute, cleaned path. Throws if
`path` does not exist.
Unlike making a path absolute, this TOUCHES THE FILESYSTEM — that is the whole
point, and it is why `cljg.io/absolute` (Go's `filepath.Abs`) is not a
substitute: `Abs` cleans a path lexically and never follows a link, while
returning something that looks canonical.
Use it to canonicalise before comparing two paths, and to guard a directory
walk against a symlink CYCLE:
(loop [[d & more] roots seen #{}]
(let [c (fs/real-path d)]
(if (contains? seen c)
(recur more seen) ; already been here — do not descend
(recur (concat more (fs/list-tree d)) (conj seen c)))))
Without it there is no cycle guard at all, and `ln -s ../.. loop` makes a walk
run forever — `list-tree` FOLLOWS directory symlinks on both hosts, verified.
Note the result may differ from the input in more than the links: macOS
resolves /tmp to /private/tmp, for instance. That is canonicalisation doing
its job — compare canonical paths to canonical paths, never to raw input.
A MISSING path throws on both hosts, and koine has to insist on that: cljgo
throws by itself, while the JVM's `.getCanonicalPath` happily cleans a path
that is not there and hands back something that LOOKS canonical. Returning
that would be the worst outcome — a caller cannot tell a resolved path from an
unresolved one, so a cycle guard would silently stop guarding.(temp-dir!)(temp-dir! prefix)Create a fresh temporary directory and return its path.
Each call returns a NEW directory — the caller owns it and should
delete-tree! it. prefix only shapes the name; never rely on the exact
path, which differs per host and per OS.
Create a fresh temporary directory and return its path. Each call returns a NEW directory — the caller owns it and should `delete-tree!` it. `prefix` only shapes the name; never rely on the exact path, which differs per host and per OS.
(write-bytes path bs)Write byte array bs to path, replacing any existing file. Returns nil.
Write byte array `bs` to `path`, replacing any existing file. Returns nil.
(write-file path s)Write string s to path, replacing any existing file. Returns nil.
Write string `s` to `path`, replacing any existing file. Returns nil.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |