(sigil fs)

Filesystem Operations

Work with files and directories: read metadata, traverse trees, find files with glob patterns, and manage temporary files. Complements (sigil io) for reading/writing and (sigil path) for path manipulation.

(import (sigil fs))

;; Read a config file
(define config (read-file-string "settings.json"))

;; Find all Scheme files
(glob "src/**/*.sgl")  ; => ("src/main.sgl" "src/lib/utils.sgl" ...)

;; Walk a directory tree
(directory-walk "project"
  (lambda (path type)
    (println "~a: ~a" type path)
    #t))  ; Return #t to recurse into directories

Exports

realpathprocedure

Return the canonicalized absolute pathname.

Resolves all symbolic links, . and .. components, and returns an absolute path. Returns #f if the path doesn't exist.

(realpath ".")           ; => "/home/user/project"
(realpath "../other")    ; => "/home/user/other"
(realpath "/tmp/../tmp") ; => "/tmp"
(realpath "nonexistent") ; => #f
path-resolveprocedure

Resolve a path relative to a base directory.

If path is absolute, returns it unchanged. Otherwise joins it with the base directory.

(path-resolve "/home/user" "file.txt")  ; => "/home/user/file.txt"
(path-resolve "/home/user" "/tmp/x")    ; => "/tmp/x"
(path-resolve "." "src/main.sgl")       ; => "./src/main.sgl"
file-exists?procedure

Check if a file or directory exists.

(file-exists? "config.txt")      ; => #t or #f
(file-exists? "nonexistent")     ; => #f
file?procedure

Check if a path is a regular file.

Returns #f for directories, symlinks, and non-existent paths.

(file? "main.sgl")   ; => #t
(file? "src")        ; => #f (it's a directory)
directory?procedure

Check if a path is a directory.

(directory? "src")       ; => #t
(directory? "main.sgl")  ; => #f

Check if a directory exists at the given path.

This is an alias for directory?, provided for naming symmetry with file-exists?.

(directory-exists? "src")       ; => #t
(directory-exists? "main.sgl")  ; => #f (not a directory)
(directory-exists? "no-such")   ; => #f (doesn't exist)
file-statprocedure

Get detailed file metadata.

Returns a list containing file information, or #f if the file doesn't exist. Pass #f as second argument to not follow symlinks. Use the stat-* accessor functions to extract specific fields.

(define info (file-stat "myfile.txt"))
(stat-size info)   ; => 1234
(stat-mtime info)  ; => 1702847123 (Unix timestamp)
(stat-type info)   ; => 'regular

;; Don't follow symlinks
(file-stat "link" #f)
file-sizeprocedure

Get file size in bytes.

Returns #f if file doesn't exist.

(file-size "data.bin")  ; => 4096
(file-size "missing")   ; => #f
file-mtimeprocedure

Get file modification time as Unix timestamp.

Returns #f if file doesn't exist.

(file-mtime "file.txt")  ; => 1702847123
file-mtime-nsprocedure

Get file modification time as nanoseconds since the Unix epoch.

On filesystems and targets that expose sub-second timestamps this is suitable for freshness comparisons between writes in the same second. Targets without that support return seconds converted to nanoseconds. Returns #f if the file doesn't exist.

(file-mtime-ns "file.txt")  ; => 1702847123123456789

Get what one stat says about whether a file's bytes may have changed: (dev inode size mtime-ns ctime-ns), from a single call.

For memoising a content hash: recompute it when any field differs. touch -d can reset mtime but not ctime, which every write moves. Follows symlinks. Returns #f if the file doesn't exist.

(file-stat-fingerprint "file.txt")
; => (66306 1835011 42 1702847123123456789 1702847123123456789)

Get one line per file under DIR, sorted by relative path: <relative path> TAB <dev> TAB <inode> TAB <size> TAB <mtime-ns> TAB <ctime-ns>.

Two equal results mean no file in the tree was added, removed, written or replaced in between. Follows symlinks. Returns #f when any part of the tree cannot be read.

(file-tree-fingerprint "build/dev/lib")
; => "sigil/core.sgb\t66306\t1835011\t48213\t...\n..."

Get one line per entry at and under PATH, sorted by relative path, without following symlinks (PATH itself is the empty path): <relative path> TAB <type> TAB <mode> TAB <size> TAB <mtime-ns> TAB <ctime-ns> TAB <link target>.

type is f, d, l, p, s, b, c or ?; mode is the permission bits in octal; the link target is empty except for a symlink. Directories are listed, so an entry added or removed shows in its directory's times. Two equal results mean nothing in the tree was written, replaced, added, removed, retargeted or had its mode changed in between. The fields are those of GNU find PATH -printf '%P\t%y\t%m\t%s\t%T@\t%C@\t%l\n' | LC_ALL=C sort, on any platform, with the times as integer nanoseconds (whole seconds where the platform's stat has no finer field).

Returns #f, rather than raising, when PATH does not exist or any part of the tree cannot be read (a partial listing never passes as a whole). Raises a type error when PATH is not a string or contains a NUL byte.

(file-tree-metadata "build/dev/lib")
; => "\td\t755\t4096\t...\t\nsigil\td\t755\t4096\t...\t\n..."
file-sha256procedure

Get the SHA-256 of a file's contents as 64 lowercase hex digits.

Returns #f when the file cannot be read.

(file-sha256 "file.txt")  ; => "2cf24dba5fb0a30e26e83b2ac5b9e29e..."
file-atimeprocedure

Get file access time as Unix timestamp.

Returns #f if file doesn't exist.

(file-atime "file.txt")  ; => 1702850000
file-ctimeprocedure

Get file creation/change time as Unix timestamp.

On Unix systems this is the inode change time (ctime), not creation time. Returns #f if file doesn't exist.

(file-ctime "file.txt")  ; => 1702847100
stat-sizeprocedure

Extract file size from a stat result.

(stat-size (file-stat "file.txt"))  ; => 1234
stat-mtimeprocedure

Extract modification time from a stat result.

(stat-mtime (file-stat "file.txt"))  ; => 1702847123
stat-atimeprocedure

Extract access time from a stat result.

(stat-atime (file-stat "file.txt"))  ; => 1702850000
stat-ctimeprocedure

Extract creation/change time from a stat result.

(stat-ctime (file-stat "file.txt"))  ; => 1702847100
stat-modeprocedure

Extract file mode (permissions) from a stat result.

Returns the raw Unix file mode bits.

(stat-mode (file-stat "script.sh"))  ; => 33261 (0o100755)
stat-uidprocedure

Extract owner user ID from a stat result.

(stat-uid (file-stat "file.txt"))  ; => 1000
stat-gidprocedure

Extract owner group ID from a stat result.

(stat-gid (file-stat "file.txt"))  ; => 1000
stat-devprocedure

Extract device ID from a stat result.

(stat-dev (file-stat "file.txt"))  ; => 2049
stat-inoprocedure

Extract inode number from a stat result.

(stat-ino (file-stat "file.txt"))  ; => 1234567
stat-typeprocedure

Extract file type from a stat result.

Returns a symbol: regular, directory, symlink, socket, fifo, block-device, char-device, or unknown.

(stat-type (file-stat "file.txt"))    ; => 'regular
(stat-type (file-stat "src"))         ; => 'directory
(stat-type (file-stat "link" #f))     ; => 'symlink
symlink?procedure

Test if path is a symbolic link.

Does not follow the link—checks the link itself.

(symlink? "mylink")     ; => #t if symlink
(symlink? "regular")    ; => #f
socket-file?procedure

Test if path is a Unix socket file.

(socket-file? "/var/run/docker.sock")  ; => #t
fifo?procedure

Test if path is a named pipe (FIFO).

(fifo? "mypipe")  ; => #t if named pipe
block-device?procedure

Test if path is a block device.

(block-device? "/dev/sda")  ; => #t
char-device?procedure

Test if path is a character device.

(char-device? "/dev/tty")  ; => #t
copy-fileprocedure

Copy a file to a new location.

Copies the contents of source to destination. The destination file is created or overwritten. Returns #t on success and #f when the source cannot be read or the destination cannot be written (a missing source, a missing destination directory).

raises: never

(copy-file "original.txt" "backup.txt")
(copy-file "src/main.sgl" "dist/main.sgl")
touchprocedure

Update file timestamps or create an empty file.

If the file exists, updates its access and modification times to now. If it doesn't exist, creates an empty file. Returns #t on success and #f when the file cannot be created or touched (a missing parent directory).

raises: never

(touch "newfile.txt")      ; Creates empty file
(touch "existing.txt")     ; Updates timestamps
delete-fileprocedure

Delete a file.

raises: io-error if the file doesn't exist or can't be deleted. Test with file-exists? first when a missing file is not an error.

(delete-file "temp.txt")
rename-fileprocedure

Rename or move a file.

raises: io-error if the source doesn't exist or the destination cannot be written.

(rename-file "old-name.txt" "new-name.txt")

Create a directory.

Creates a single directory. Parent directories must exist. Use ensure-directory to create parent directories as needed.

raises: io-error if a parent is missing or the directory cannot be created.

(make-directory "output")

List the contents of a directory.

Returns a list of filenames (not full paths). Does not include . or ...

(directory-list "src")  ; => ("main.sgl" "utils.sgl" "lib")

Read entire file contents as a string.

Convenient for small to medium files. For large files or streaming, use (sigil io) port functions instead.

raises: io-error if the file cannot be opened, unless missing: is given, in which case a missing file returns that value instead (other errors still raise); error if PATH is a directory.

(define config (read-file-string "config.json"))
(define source (read-file-string "src/main.sgl"))
(read-file-string "optional.cfg" missing: #f)  ; => #f when absent

MISSING applies only to absent files. Other errors still propagate. TRIM removes surrounding whitespace from successfully read text.

Read entire file contents as a bytevector.

For binary files like images, audio, etc.

raises: io-error if the file cannot be opened, unless missing: is given, in which case a missing file returns that value instead.

(define image (read-file-bytes "photo.png"))
(read-file-bytes "cache.bin" missing: #f)  ; => #f when absent

Write a string to a file.

Creates the file if it doesn't exist, or replaces its contents if it does. Parent directories must exist.

raises: io-error if the file cannot be opened for writing (a missing parent directory, no permission).

(write-file-string "output.txt" "Hello, World!")
(write-file-string "data.json" (json-encode data))

Write a bytevector to a file.

Creates the file if it doesn't exist, or replaces its contents. Parent directories must exist.

raises: io-error if the file cannot be opened for writing.

(write-file-bytes "output.bin" my-bytevector)

Remove an empty directory.

The directory must be empty. Use directory-walk to remove contents first if needed. Returns #t on success and #f when the directory is missing or not empty.

raises: never

(delete-directory "empty-dir")

Create directory and all parent directories.

Like mkdir -p—creates any missing parent directories. Does nothing if the directory already exists.

raises: io-error if a directory on the path cannot be created.

(ensure-directory "build/lib/sigil")
(ensure-directory "output")

Walk a directory tree, calling a procedure on each entry.

The procedure receives two arguments: the full path and a type symbol (file, directory, or other). For directories, return #t to recurse into them, or #f to skip.

;; Print all files
(directory-walk "src"
  (lambda (path type)
    (when (eq? type 'file)
      (println "~a" path))
    #t))

;; Count files, skip hidden directories
(let ((count 0))
  (directory-walk "project"
    (lambda (path type)
      (cond
        ((eq? type 'file) (set! count (+ count 1)) #t)
        ((eq? type 'directory)
         (not (string-starts-with? (path-basename path) ".")))
        (else #t))))
  count)

Collect all paths in a directory tree.

Returns a flat list of all files and directories, recursively.

(directory-walk-list "src")
; => ("src/main.sgl" "src/lib" "src/lib/utils.sgl" ...)

Find files matching a predicate.

Walks the directory tree and returns paths where the predicate returns #t. The predicate receives path and type.

;; Find all .sgl files
(directory-find "src"
  (lambda (path type)
    (and (eq? type 'file)
         (string-ends-with? path ".sgl"))))

;; Find empty directories
(directory-find "project"
  (lambda (path type)
    (and (eq? type 'directory)
         (null? (directory-list path)))))
find-filesprocedure

Recursively find all files in a directory.

Returns a list of all file paths (not directories) under the given path, optionally filtered by a predicate. Hidden files (starting with .) are skipped by default.

;; All files recursively
(find-files "src")
; => ("src/main.sgl" "src/lib/utils.sgl" ...)

;; Filter to only .md files
(find-files "docs"
  (lambda (path) (string-ends-with? path ".md")))
; => ("docs/index.md" "docs/guide/intro.md" ...)
globprocedure

Find files matching a glob pattern. Returns a list of matching file paths.

Supports shell-style wildcards: * matches any characters except /, and ** matches any path including subdirectories.

(glob "*.sgl")           ; Files in current directory
(glob "src/*.sgl")       ; Files in src/
(glob "src/**/*.sgl")    ; All .sgl files recursively
(glob "test/*-test.sgl") ; Test files

Create a temporary file.

Returns the path to a new unique temporary file. The file is created empty. Remember to delete it when done.

(define tmp (make-temp-file))
(write-file-string tmp "temporary data")
;; ... use the file ...
(delete-file tmp)

Create a temporary directory.

Returns the path to a new unique temporary directory. Remember to remove it when done (it must be empty first).

(define tmp-dir (make-temp-directory))
;; ... use the directory ...
(delete-directory tmp-dir)

Use a temporary file, then automatically delete it.

Creates a temp file, calls proc with its path, deletes the file, and returns the result of proc.

(call-with-temp-file
  (lambda (path)
    (write-file-string path "test data")
    (run-external-tool path)))

Get the current working directory.

(current-directory)  ; => "/home/user/project"

Change the current working directory.

(set-current-directory! "/tmp")
(current-directory)  ; => "/tmp"

Execute code in a different directory, then restore.

Changes to the given directory, runs thunk, then restores the original directory regardless of how thunk exits.

raises: io-error if the directory cannot be entered (missing, not a directory, no permission); the thunk does not run.

(with-current-directory "/tmp"
  (lambda ()
    (write-file-string "output.txt" "data")
    (current-directory)))
; => "/tmp"

(current-directory)
; => "/home/user/project" (restored)

(No description)

(No description)