Dir API

Directory I/O through static path helpers and directory handles, with entry queries, mutations, and two styles of enumeration.

Overview

The global identifier Dir is a namespace of static helpers and opener constructors. Static helpers operate on a path directly, without opening anything. Opening a directory returns a directory handle whose instance methods are invoked as d.method(...); the handle carries a current directory, listing filters, and iterator state.

Conventions

See also the site-wide CLI conventions. The points below are specific to Dir:

Opening Directories

Dir.open(path)

Opens the directory at path and returns a directory handle. Returns null on failure.

Dir.openTemp(prefix, keep)

Creates a new temporary directory and returns a handle to it, or null on failure. prefix is a leaf-name prefix for the generated directory. When keep is false the directory is cleaned up when the handle is released; when true it is left in place.

var d = Dir.open("src")
if (d == null) { return }  // open failed

var t = Dir.openTemp("zym_", false)  // removed when handle is released

Static Helpers

Convenience wrappers that operate on a path without opening a handle.

Dir.exists(path)

Returns true if path resolves to an existing directory.

Dir.makeDir(path)

Creates a single directory at path. The parent directory must already exist. Returns true on success.

Dir.makeDirRecursive(path)

Creates the directory at path and any missing parents. Returns true on success.

Dir.copy(src, dst)

Copies a single file at src to dst. Returns true on success.

Dir.rename(src, dst)

Renames or moves src to dst. Returns true on success.

Dir.remove(path)

Removes a file or an empty directory at path. Refuses to delete a non-empty directory. Returns true on success.

Dir.files(path)

Returns the names of regular files directly under path as a list of strings.

Dir.directories(path)

Returns the names of subdirectories directly under path as a list of strings.

Dir.driveCount()

Returns the number of logical drives. Platform-dependent; 0 on Unix-likes.

Dir.driveName(idx)

Returns the name of drive idx; empty string if idx is out of range.

Non-empty directories: Dir.remove only removes empty directories. To clear a directory tree, open a handle and call eraseContentsRecursive(), or pair Dir.remove with a recursive enumeration of files() / directories().
path helpers
if (!Dir.exists("build")) {
    Dir.makeDir("build")
}
Dir.makeDirRecursive("build/out/logs")  // creates any missing parents
Dir.copy("config.zym", "build/config.zym")
Dir.rename("build/out", "build/dist")

Handle State

d.path()

Returns the absolute path of the handle's current directory.

d.changeDir(path)

Navigates the handle to path; resolves . and ... Returns false if the target doesn't exist.

Queries on Entries

Entry-relative queries. name is resolved against the handle's current directory.

d.fileExists(name)

Returns true if name is a regular file. Follows symlinks.

d.dirExists(name)

Returns true if name is a directory. Follows symlinks.

d.isReadable(name)

Readability query for name. Returns a bool.

d.isWritable(name)

Writability query for name. Returns a bool.

Returns true if name is a symbolic link. Reports the link itself and does not follow it.

Returns the target path of a symbolic link; empty string if name is not a link.

d.spaceLeft()

Returns the number of free bytes on the filesystem containing the current directory.

if (d.isLink("current")) {
    print(d.readLink("current"))  // target of the link
}
var free = d.spaceLeft()  // free bytes on this filesystem

Mutations

d.makeDir(path)

Creates a single directory under the current directory, or at an absolute path. Returns true on success.

d.makeDirRecursive(path)

Creates the directory and any missing parents. Returns true on success.

d.copy(src, dst)

Copies a single file. Returns true on success.

d.rename(src, dst)

Renames or moves an entry. Returns true on success.

d.remove(name)

Removes a file or an empty directory. Refuses to delete a non-empty directory. Returns true on success.

d.eraseContentsRecursive()

Recursively deletes every entry inside the current directory; the directory itself is kept. Returns true on success.

Destructive: eraseContentsRecursive is non-recoverable. Entries are not moved to a trash or recycle bin. Confirm d.path() before calling it.

Creates a symbolic link at dst pointing at src. Returns true on success. May fail without elevated privileges on Windows; on Unix-likes symlink creation is unprivileged.

clear a scratch directory
var scratch = Dir.open("build/tmp")
if (scratch != null) {
    scratch.eraseContentsRecursive()  // contents gone, build/tmp kept
}

Enumeration

Two styles are supported. Pick whichever fits the script.

Snapshot Style

Returns the whole listing as a list of names.

d.files()

Returns the names of regular files in the current directory as a list of strings.

d.directories()

Returns the names of subdirectories in the current directory as a list of strings.

Iterator Style

Walks one entry at a time and exposes per-entry metadata without allocating a list. Between listBegin and listEnd, each call to listNext advances the iterator, and listCurrentIsDir / listCurrentIsHidden then reflect the entry just returned.

d.listBegin()

Starts iteration. Returns false if the directory can't be opened.

d.listNext()

Returns the next entry name, or null when enumeration is finished.

d.listCurrentIsDir()

Returns true if the entry returned by the last listNext is a directory.

d.listCurrentIsHidden()

Returns true if the entry is hidden (leading . on Unix, hidden attribute on Windows).

d.listEnd()

Releases iterator state; returns null. Safe to call multiple times, and even after listNext returned null. Always pair with listBegin, since leaving an iterator open holds a directory handle at the OS level.

iterator walk
if (d.listBegin()) {
    var name = d.listNext()
    while (name != null) {
        if (d.listCurrentIsDir()) {
            print("dir : %s", name)
        } else {
            print("file: %s", name)
        }
        name = d.listNext()
    }
    d.listEnd()
}

Listing Filters

Filters apply to both snapshot and iterator enumeration.

d.setIncludeNavigational(b)

If true, . and .. are included in listings. Default: false. Returns null.

d.setIncludeHidden(b)

If true, hidden entries are included in listings. Default: false. Returns null.

d.includeNavigational()

Returns the current navigational-entries setting.

d.includeHidden()

Returns the current hidden-entries setting.

d.setIncludeHidden(true)
var all = d.files()  // now includes dotfiles

Filesystem Metadata

d.isCaseSensitive(path)

Returns whether names under path are case-sensitive. Platform- and filesystem-dependent.

d.filesystemType()

Returns the platform name of the filesystem hosting the current directory (e.g. "ext4", "NTFS", "APFS"). Empty string if unavailable.

Drives

d.driveCount()

Returns the number of logical drives.

d.drive(idx)

Returns the name of drive idx.

d.currentDrive()

Returns the index of the drive backing the current directory.

Platform: drive APIs are primarily meaningful on Windows. On Unix-likes driveCount() is 0 and drive(idx) returns an empty string.

Notes & Gotchas

Handle aliasing. Assigning a Dir handle (d2 = d1) aliases the underlying directory; operations on either reference affect the same position and filter state. Open a second handle with Dir.open for independent iteration.

Closing is implicit. The directory handle is released when the last reference is dropped. There is no explicit close(); terminate iterators with listEnd if the handle is kept alive.

Symlinks. fileExists and dirExists follow symlinks; isLink reports the link itself. Use readLink to get the target.

Hidden and navigational entries are filtered by default. ., .., and hidden names do not appear in files() / directories() / listNext() unless enabled via setIncludeNavigational(true) / setIncludeHidden(true).

Examples

Listing a Directory

var d = Dir.open(".")
if (d == null) {
    print("cannot open cwd")
} else {
    d.setIncludeHidden(false)

    var dirs = d.directories()
    for (var i = 0; i < length(dirs); i = i + 1) {
        print("dir : %s", dirs[i])
    }

    var files = d.files()
    for (var i = 0; i < length(files); i = i + 1) {
        print("file: %s", files[i])
    }
}

Temporary Workspace

if (!Dir.exists("build/tmp")) {
    Dir.makeDirRecursive("build/tmp")
}

var t = Dir.openTemp("zym_", false)  // auto-cleaned when handle drops
t.makeDir("cache")