Path API

Pure-string utilities for manipulating filesystem paths portably across operating systems.

Overview

The Path API is a namespace of pure-string path utilities, registered at VM startup as the global identifier Path. All methods are invoked as Path.method(...), and every method is a string-in / string-or-bool-out transformation. Path never accesses the filesystem. Anything that asks “does it exist?” / “is it readable?” / “how big is it?” lives in File (File.exists, File.size, File.modificationTime) and Dir (Dir.exists, Dir.list, Dir.makeRecursive). Path is exclusively a string library.

For OS-specific values that feed these utilities (user home, data/config/cache directories, executable path, environment variables) see System. Path.join(System.dataDir(), ...) is the recommended pattern for building portable per-user output paths.

Conventions

Strings only. Every input is a string; every result is a string or boolean. Bad argument types raise a Zym runtime error of the form Path.method(args) expects a string.

No filesystem I/O. Path.normalize does not resolve symlinks or check existence; it is purely textual .. / . / multi-slash collapse. To follow symlinks or canonicalize against the live filesystem, combine Path.normalize with Dir.exists / File.exists checks.

POSIX-style names. “basename” means different things in different ecosystems, so this API exposes both forms under explicit names: stem for the trailing component minus extension, and basename for the whole path minus extension. dirname / filename / extension follow POSIX basename, Python os.path, and Node path conventions.

Cross-platform separator. Methods that build paths use the host platform's native separator (/ on Linux/macOS, \ on Windows). A literal / in a script is accepted as a separator on every platform for path input, but Path.separator() is the source of truth for the output separator on the current host.

Querying Paths

Path.isAbsolute(p)

Returns true if p starts at a root (Unix /foo, Windows C:\foo).

Path.isRelative(p)

Returns the inverse of isAbsolute. The empty string returns true, since it is relative.

Path.isNetworkShare(p)

Returns true for UNC paths like //server/share or \\server\share.

query predicates
Path.isAbsolute("/etc/hosts")            // true
Path.isAbsolute("logs/run.txt")          // false
Path.isRelative("")                      // true (empty string is relative)
Path.isNetworkShare("//server/share")    // true (UNC path)

Splitting Paths

Path.dirname(p)

Returns everything up to (but not including) the trailing component. Path.dirname("/a/b/c.txt") returns "/a/b"; Path.dirname("/") returns "/".

Path.filename(p)

Returns the trailing component. Path.filename("/a/b/c.txt") returns "c.txt"; Path.filename("/a/b/") returns "".

Path.extension(p)

Returns the rightmost extension, without the leading dot: "/a/b.tar.gz" yields "gz". Returns an empty string if there is no extension.

Path.stem(p)

Returns the trailing component minus the rightmost extension. "/a/b/c.txt" yields "c"; "/a/b.tar.gz" yields "b.tar".

Path.basename(p)

Returns the whole path minus the rightmost extension. "/a/b/c.txt" yields "/a/b/c". Use stem for only the trailing component without its extension.

Rightmost extension only: multi-suffix files like archive.tar.gz report "gz", with stem = "archive.tar". This matches POSIX and Python conventions.
inspect a path
var p = "/var/log/zym/run.2024-01-15.log"
print("dir : %s", Path.dirname(p))         // /var/log/zym
print("file: %s", Path.filename(p))        // run.2024-01-15.log
print("stem: %s", Path.stem(p))            // run.2024-01-15
print("ext : %s", Path.extension(p))       // log

Building Paths

Path.join(...)

Joins any number of string segments into one path using the host platform's native separator. Empty segments are skipped. If a later segment is itself absolute, it rebases the result, matching POSIX os.path.join. Calling Path.join() with no arguments returns "".

Path.join("a", "", "b")      // "a/b" on Linux/macOS
Path.join("a/b", "/etc")     // "/etc" (absolute segment rebases)
Path.join()                   // ""
Path.normalize(p)

Textually resolves . and .. segments, collapses doubled separators, and trims a trailing separator. Does not touch the filesystem: symbolic links are not resolved, ~ is not dereferenced, and existence is never checked. Compose with Path.expandUser and Dir.exists / File.exists for richer behavior.

Path.relative(from, to)

Returns the relative path from from to to. from is treated as a file: its parent directory is the basis for the result. Path.relative("/a/b", "/a/c") returns "../c". For directory-to-directory relative paths, append an explicit trailing component to from. See the worked example below.

Path.withExtension(p, ext)

Replaces (or appends) the rightmost extension.

var out = Path.withExtension("/tmp/data.json", "min.json.gz")
// out -> "/tmp/data.min.json.gz"
Path.expandUser(p)

Expands a leading ~ or ~/... to the current user's home directory ($HOME on Unix, %USERPROFILE% or %HOMEDRIVE%%HOMEPATH% on Windows). Non-tilde paths pass through unchanged.

Current user only: the ~user form is intentionally not expanded. The host has no portable way to resolve another user's home from script-side without shelling out. If needed, drive getent passwd <user> via Process.exec.
tilde-rooted path
var cfg = Path.normalize(Path.expandUser("~/.config/my-tool"))
// cfg -> "/home/me/.config/my-tool" on Linux

Separator

Path.separator()

Returns the host platform's path separator: "/" on Linux/macOS, "\\" on Windows. Rarely needed in scripts that use Path.join.

Examples

Build a Per-User Output Path

var dir = Path.join(System.dataDir(), "my-tool")
Dir.makeRecursive(dir)
File.writeAllText(Path.join(dir, "config.json"), "{}")

Compute a Relative Path

var rel = Path.relative("/srv/app/web/index.html", "/srv/app/static/logo.svg")
// rel -> "../static/logo.svg"

// relative() treats the from argument as a file. To compare two
// directories, append a sentinel trailing component:
var fromDir = "/srv/app/web"
rel = Path.relative(Path.join(fromDir, ".sentinel"), "/srv/app/static/logo.svg")
// rel -> "../static/logo.svg"