babashka.fs

repository·master·Indexed 42 days ago

https://github.com/babashka/fs

A cross-platform file system utility library for Clojure on the JVM, Babashka, and Node.js. It provides an ergonomic wrapper around java.nio for JVM users and a synchronous, scripting-focused API for Node.js via the @babashka/fs package (v0.5.34). Features include glob pattern matching, POSIX permission management, symbolic link handling, and high-precision file time representation using BigInt on Node.js.

Tokens
3.6K
Snippets
9
Records
18
Agent score
43%

What's inside babashka.fs

  1. Understand Node.js parity divergences in babashka.fs

    master

    When using the Node.js port of babashka.fs, certain behaviors may diverge from the JVM (reference) implementation. These divergences are documented and accepted based on the synchronous design constraints of the Node.js port or platform-specific limitations (e.g., Windows vs. Unix).

    Key areas of divergence include:

    • File Time Precision: File times are written with millisecond precision on Node.js, whereas the JVM uses nanoseconds.
    • Attribute Views: get-attribute on Node.js provides a basic view (times, size, kind) and may return nil for specialized views like unix: or posix: permissions.
    • Path Normalization: (parent "./foo") returns nil on Node.js, whereas the JVM returns "良".
    • Atomic Moves: The :atomic-move option in move is ignored on Node.js; renameSync is used instead. Cross-filesystem moves will throw EXDEV on Node.js rather than falling back to copy-then-delete.
    • Compression Memory Usage: gzip and gunzip operations buffer the entire file in memory on Node.js, unlike the streaming approach used on the JVM.
  2. How babashka.fs handles path arguments and returns

    master

    Coercions

    Functions automatically coerce input path arguments from File, Path, and String. These are all equivalent:

    (fs/exists? "foo")
    (fs/exists? (java.io.File. "foo"))
    (fs/exists? (.toPath (java.io.File. "foo")))

    Explicit Returns

    If you need a specific concrete type for a returned path, use the following coercion functions:

    • (fs/path path): returns a Path object.
    • (fs/file path): returns a File object.
    • (fs/str path): returns a String.

    Example using cwd (current working directory):

    (str (fs/cwd))
    (fs/path (fs/cwd))
    (fs/file (fs/cwd))
  3. Handle file permissions and hidden status on Windows Node.js

    master

    When running on Windows via Node.js, be aware of the following behaviors:

    • Hidden Files: hidden? treats files starting with a dot (.) as hidden on all platforms, including Windows. This differs from the JVM, which relies on the DOS hidden attribute.
    • Read-only Trees: delete-tree with the :force option may fail to remove a directory tree on Windows if the files are marked as read-only. On the JVM, :force clears the read-only attribute, but on Node.js Windows, it only ignores missing paths and does not clear the attribute.
  4. Understand file time representation on Node.js

    master

    When using babashka.fs on Node.js, file times (returned by last-modified-time, creation-time, and read-attributes) are represented as BigInt nanoseconds since the Unix epoch.

    This differs from the JVM implementation, which uses java.nio.file.attribute.FileTime. This BigInt representation ensures nanosecond precision is preserved during reads, allowing functions like modified-since to correctly compare files on filesystems with sub-millisecond timestamps.

    Key behaviors to note:

    • Read Precision: Full filesystem precision (nanoseconds) is available via BigInt.
    • Write Precision: Writing file times (e.g., via set-last-modified-time) is limited to millisecond precision because Node's native utimesSync API does not support nanoseconds. A round-trip of set-last-modified-time followed by last-modified-time will result in millisecond truncation.
    • Type Handling: Do not assume file times are js/Date objects on Node.js. Use the provided conversion functions to transform them into usable formats.
  5. Understand the dual export surface of @babashka/fs

    master

    The @babashka/fs npm package provides two distinct ways to access its functions, depending on whether you are using standard JavaScript or Squint (Clojure-to-JS compiler):

    1. JS-idiomatic surface (camelCase): Use these for standard JavaScript development. These wrappers accept camelCase or dashed-case keys in option objects.
    2. Squint interop surface (munged names): These are the raw functions exported from the underlying module. They use names like copy_tree or exists_QMARK_ and require dashed string keys (e.g., {:replace-existing true}) in option objects.

    Warning for JS users: If you call a munged function directly (e.g., fs.copy_tree(...)) using camelCase options, the options will be silently ignored because the raw functions do not translate keys.

  6. Handle symbolic links with :nofollow-links and :follow-links

    master

    Many functions accept options to control how symbolic links are handled:

    • :nofollow-links true: The function will not follow symbolic links. Use this to perform operations on the link itself rather than the target. Note that some JDK/OS combinations may throw an error when attempting to set attributes on a link itself.
    • :follow-links true: The function will follow symbolic links (this is often the default behavior).

    Warning: On some JDK 11 (macOS/Linux) and JDK 21 (Linux) versions, attempting to set attributes on a link using :nofollow-links true may result in a FileSystemException (e.g., Too many levels of symbolic links).

    (fs/set-last-modified-time "foo-link" (java.time.Instant/now) {:nofollow-links true})
  7. Use the correct naming and option patterns for @babashka/fs

    master

    Depending on your environment, follow these patterns to ensure options are correctly applied:

    For JavaScript Users

    Use the camelCase function names and camelCase option keys.

    // Correct usage for JS
    fs.copyTree(src, dst, { replaceExisting: true });
    
    // INCORRECT (options will be ignored)
    fs.copy_tree(src, dst, { replaceExisting: true });

    For Squint Users

    Use the munged function names and dashed-string option keys.

    ;; Correct usage for Squint
    (fs/copy-tree src dst {:replace-existing true})
    
    ;; Which resolves to the following JS call:
    fs.copy_tree(src, dst, { "replace-existing": true });
  8. Use copy-tree for directory copying

    master

    To copy a directory and its contents, use copy-tree.

    Important Notes:

    • Self-copying: Calling (copy-tree d d) (copying a directory onto itself) is a safe no-op that returns the target directory without throwing errors.
    • Attributes: Using :copy-attributes true will preserve file modification times (mtimes), but it will not preserve the modification time of the directories themselves (directory mtimes will reflect the time of the copy operation).
    • Directory Source: Do not use the standard copy function with a directory source on Node.js, as it will throw an EISDIR error. Use copy-tree instead.
  9. Install babashka.fs

    master

    JVM Clojure

    Add the library to your deps.edn file:

    ;; deps.edn
    babashka/fs {:mvn/version "<version>"}

    Babashka

    It is a built-in library in babashka (since v0.2.9) and works out of the box.

    Node.js

    Install via npm:

    npm install @babashka/fs

    Note: On Node.js, all operations are synchronous. If you require async support, this library is not suitable. It is intended for scripting (e.g., using nbb).

  10. Manage POSIX file permissions and umask

    master

    On Linux and macOS, you can specify :posix-file-permissions when creating files or directories. Note that these permissions are subject to your system's umask.

    Example: Creation with umask interference

    If your umask masks out others-write permission:

    ;; Even if we request rwxrwxrwx, umask will modify it
    (fs/create-file "afile" {:posix-file-permissions "rwxrwxrwx"})
    
    ;; Resulting permissions might be "rwxrwxr-x"
    (-> (fs/posix-file-permissions "afile") (fs/posix->str))

    Overriding umask for existing files

    To explicitly set permissions on an existing file or directory (bypassing umask), use set-posix-file-permissions:

    (fs/set-posix-file-permissions "adir" "rwxrwxrwx")
    (fs/create-file "afile" {:posix-file-permissions "rwxrwxrwx"})
  11. Use babashka.fs in JavaScript/Node.js

    master

    In Node.js, the library is available via the @babashka/fs npm package. The API is synchronous and follows JavaScript conventions.

    Naming Conventions

    Functions are exported as camelCase and kebab_case. Predicates use is prefixes or drop the ?.

    ClojureJavaScript
    (fs/copy-tree src dst)fs.copyTree(src, dst)
    (fs/directory? p)fs.isDirectory(p)
    (fs/exists? p)fs.exists(p)
    (fs/copy src dst {:replace-existing true})fs.copy(src, dst, {replaceExisting: true})

    Example Usage

    import * as fs from '@babashka/fs';
    fs.exists("README.md") //=> true

    Node.js Specifics

    • Paths are always strings.
    • File times are BigInt nanoseconds.
    • zip/unzip are not supported.
    • set-creation-time is not supported.
    • owner returns a uid instead of a username.
    • The atomic-move option is ignored.
  12. Use @babashka/fs in JavaScript/Node.js

    master

    The @babashka/fs library provides a comprehensive file system API for JavaScript consumers. While the underlying implementation uses ClojureScript (via squint), the public API is exported using friendly camelCase names.

    Key usage patterns:

    • CamelCase Options: When passing options objects to functions, use camelCase keys (e.g., { keep: true } or { force: true }). The library automatically translates these to the required internal kebab-case format.
    • Squint Interop: For advanced interop, the library also re-exports the raw munged names (e.g., copy_tree, exists_QMARK_) directly.