2026-01-26 05:12:14 +01:00
{# retoor < retoor @ molodetz . nl > #}
{% extends 'page.html' %}
2026-01-25 04:58:39 +01:00
2026-01-26 05:12:14 +01:00
{% set page_title = "pathlib" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "pathlib"}] %}
{% set prev_page = {"url": "api/io.html", "title": "io"} %}
{% set next_page = {"url": "api/scheduler.html", "title": "scheduler"} %}
{% block article %}
2026-01-25 04:58:39 +01:00
< h1 > pathlib</ h1 >
< p > The < code > pathlib</ code > module provides object-oriented filesystem path operations. Inspired by Python's < code > pathlib</ code > , it offers immutable < code > Path</ code > objects that combine path manipulation with filesystem I/O in a single interface.</ p >
< pre >< code > import "pathlib" for Path</ code ></ pre >
< div class = "toc" >
< h4 > On This Page</ h4 >
< ul >
< li >< a href = "#purepath-class" > PurePath Class</ a ></ li >
< li >< a href = "#path-class" > Path Class</ a ></ li >
< li >< a href = "#path-properties" > Path Properties</ a ></ li >
< li >< a href = "#path-manipulation" > Path Manipulation</ a ></ li >
< li >< a href = "#filesystem-operations" > Filesystem Operations</ a ></ li >
< li >< a href = "#directory-operations" > Directory Operations</ a ></ li >
< li >< a href = "#symlink-operations" > Symlink Operations</ a ></ li >
< li >< a href = "#examples" > Examples</ a ></ li >
</ ul >
</ div >
< h2 id = "purepath-class" > PurePath Class</ h2 >
< div class = "class-header" >
< h3 > PurePath</ h3 >
< p > String-only path operations without filesystem access. Base class for < code > Path</ code > .</ p >
</ div >
< h3 > Constructor</ h3 >
< div class = "method-signature" >
< span class = "method-name" > PurePath.new</ span > (< span class = "param" > path</ span > ) → < span class = "type" > PurePath</ span >
</ div >
< p > Creates a new PurePath from a string or another PurePath.</ p >
< pre >< code > var p = PurePath.new("/home/user/file.txt")</ code ></ pre >
< h3 > Properties</ h3 >
< div class = "method-signature" >
< span class = "method-name" > parts</ span > → < span class = "type" > List</ span >
</ div >
< p > The individual components of the path.</ p >
< pre >< code > var p = Path.new("/home/user/file.txt")
System.print(p.parts) // [/, home, user, file.txt]</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > name</ span > → < span class = "type" > String</ span >
</ div >
< p > The final component of the path (filename with extension).</ p >
< pre >< code > var p = Path.new("/home/user/file.tar.gz")
System.print(p.name) // file.tar.gz</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > stem</ span > → < span class = "type" > String</ span >
</ div >
< p > The filename without the last extension.</ p >
< pre >< code > var p = Path.new("/home/user/file.tar.gz")
System.print(p.stem) // file.tar</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > suffix</ span > → < span class = "type" > String</ span >
</ div >
< p > The last file extension, including the leading dot.</ p >
< pre >< code > var p = Path.new("/home/user/file.tar.gz")
System.print(p.suffix) // .gz</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > suffixes</ span > → < span class = "type" > List</ span >
</ div >
< p > All file extensions as a list.</ p >
< pre >< code > var p = Path.new("/home/user/file.tar.gz")
System.print(p.suffixes) // [.tar, .gz]</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > parent</ span > → < span class = "type" > Path</ span >
</ div >
< p > The logical parent of the path.</ p >
< pre >< code > var p = Path.new("/home/user/file.txt")
System.print(p.parent) // /home/user</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > parents</ span > → < span class = "type" > List</ span >
</ div >
< p > A list of all ancestor paths, from immediate parent to root.</ p >
< pre >< code > var p = Path.new("/home/user/docs/file.txt")
for (ancestor in p.parents) {
System.print(ancestor)
}
// /home/user/docs
// /home/user
// /home
// /</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > drive</ span > → < span class = "type" > String</ span >
</ div >
< p > The drive letter (Windows only). Returns empty string on POSIX.</ p >
< div class = "method-signature" >
< span class = "method-name" > root</ span > → < span class = "type" > String</ span >
</ div >
< p > The root of the path. Returns < code > "/"</ code > for absolute paths, empty string for relative.</ p >
< div class = "method-signature" >
< span class = "method-name" > anchor</ span > → < span class = "type" > String</ span >
</ div >
< p > The concatenation of drive and root.</ p >
< div class = "method-signature" >
< span class = "method-name" > isAbsolute</ span > → < span class = "type" > Bool</ span >
</ div >
< p > True if the path is absolute (has a root).</ p >
< pre >< code > System.print(Path.new("/etc/hosts").isAbsolute) // true
System.print(Path.new("relative/path").isAbsolute) // false</ code ></ pre >
< h2 id = "path-manipulation" > Path Manipulation</ h2 >
< div class = "method-signature" >
< span class = "method-name" > path</ span > / < span class = "param" > other</ span > → < span class = "type" > Path</ span >
</ div >
< p > Joins two paths using the < code > /</ code > operator. If < code > other</ code > is absolute, it replaces the current path.</ p >
< pre >< code > var base = Path.new("/home/user")
var full = base / "documents" / "file.txt"
System.print(full) // /home/user/documents/file.txt</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > joinpath</ span > (< span class = "param" > other</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Joins paths. Accepts a string, Path, or List of components.</ p >
< pre >< code > var p = Path.new("/home").joinpath("user").joinpath("file.txt")
System.print(p) // /home/user/file.txt
var q = Path.new("/home").joinpath(["user", "docs", "file.txt"])
System.print(q) // /home/user/docs/file.txt</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > withName</ span > (< span class = "param" > newName</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Returns a new path with the filename replaced.</ p >
< pre >< code > var p = Path.new("/home/user/old.txt")
System.print(p.withName("new.txt")) // /home/user/new.txt</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > withStem</ span > (< span class = "param" > newStem</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Returns a new path with the stem replaced, keeping the extension.</ p >
< pre >< code > var p = Path.new("/data/archive.tar.gz")
System.print(p.withStem("backup")) // /data/backup.gz</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > withSuffix</ span > (< span class = "param" > newSuffix</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Returns a new path with the extension replaced.</ p >
< pre >< code > var p = Path.new("/data/file.txt")
System.print(p.withSuffix(".md")) // /data/file.md</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > relativeTo</ span > (< span class = "param" > base</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Returns a relative path from < code > base</ code > to this path. Aborts if the path is not relative to < code > base</ code > .</ p >
< pre >< code > var p = Path.new("/home/user/docs/file.txt")
System.print(p.relativeTo("/home/user")) // docs/file.txt</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > match</ span > (< span class = "param" > pattern</ span > ) → < span class = "type" > Bool</ span >
</ div >
< p > Matches the filename against a glob pattern. Supports < code > *</ code > and < code > ?</ code > wildcards.</ p >
< pre >< code > var p = Path.new("/home/user/notes.txt")
System.print(p.match("*.txt")) // true
System.print(p.match("*.md")) // false</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > asPosix</ span > → < span class = "type" > String</ span >
</ div >
< p > Returns the path string with forward slashes (converts backslashes on Windows).</ p >
< div class = "method-signature" >
< span class = "method-name" > expanduser</ span > () → < span class = "type" > Path</ span >
</ div >
< p > Expands a leading < code > ~</ code > to the user's home directory.</ p >
< pre >< code > var p = Path.new("~/.config/app")
System.print(p.expanduser()) // /home/alice/.config/app</ code ></ pre >
< h3 > Operators</ h3 >
< div class = "method-signature" >
< span class = "method-name" > ==</ span > (< span class = "param" > other</ span > ) → < span class = "type" > Bool</ span >
</ div >
< p > Compares two paths for equality by their string representation.</ p >
< div class = "method-signature" >
< span class = "method-name" > !=</ span > (< span class = "param" > other</ span > ) → < span class = "type" > Bool</ span >
</ div >
< p > Compares two paths for inequality.</ p >
< div class = "method-signature" >
< span class = "method-name" > toString</ span > → < span class = "type" > String</ span >
</ div >
< p > Returns the path as a string.</ p >
< h2 id = "path-class" > Path Class</ h2 >
< div class = "class-header" >
< h3 > Path</ h3 >
< p > Extends PurePath with filesystem operations. This is the primary class for working with paths.</ p >
</ div >
< h3 > Constructor</ h3 >
< div class = "method-signature" >
< span class = "method-name" > Path.new</ span > (< span class = "param" > path</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Creates a new Path from a string or another Path.</ p >
< pre >< code > var p = Path.new("/home/user/file.txt")</ code ></ pre >
< h3 > Class Properties</ h3 >
< div class = "method-signature" >
< span class = "method-name" > Path.cwd</ span > → < span class = "type" > Path</ span >
</ div >
< p > The current working directory as a Path.</ p >
< pre >< code > System.print(Path.cwd) // /home/user/projects</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > Path.home</ span > → < span class = "type" > Path</ span >
</ div >
< p > The current user's home directory as a Path.</ p >
< pre >< code > System.print(Path.home) // /home/alice</ code ></ pre >
< h2 id = "filesystem-operations" > Filesystem Operations</ h2 >
< div class = "method-signature" >
< span class = "method-name" > exists</ span > () → < span class = "type" > Bool</ span >
</ div >
< p > Returns true if the path points to an existing file or directory.</ p >
< pre >< code > if (Path.new("/etc/hosts").exists()) {
System.print("File exists")
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > isFile</ span > () → < span class = "type" > Bool</ span >
</ div >
< p > Returns true if the path points to a regular file.</ p >
< div class = "method-signature" >
< span class = "method-name" > isDir</ span > () → < span class = "type" > Bool</ span >
</ div >
< p > Returns true if the path points to a directory.</ p >
< div class = "method-signature" >
< span class = "method-name" > isSymlink</ span > () → < span class = "type" > Bool</ span >
</ div >
< p > Returns true if the path is a symbolic link.</ p >
< div class = "method-signature" >
< span class = "method-name" > stat</ span > () → < span class = "type" > Stat</ span >
</ div >
< p > Returns a Stat object with file metadata (size, mode, timestamps).</ p >
< pre >< code > var s = Path.new("/etc/hosts").stat()
System.print(s.size) // 234</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > lstat</ span > () → < span class = "type" > Stat</ span >
</ div >
< p > Like < code > stat()</ code > but does not follow symbolic links.</ p >
< div class = "method-signature" >
< span class = "method-name" > readText</ span > () → < span class = "type" > String</ span >
</ div >
< p > Reads the entire file contents as a string.</ p >
< pre >< code > var content = Path.new("config.json").readText()
System.print(content)</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > readBytes</ span > () → < span class = "type" > String</ span >
</ div >
< p > Reads the entire file contents as bytes.</ p >
< div class = "method-signature" >
< span class = "method-name" > writeText</ span > (< span class = "param" > content</ span > )
</ div >
< p > Writes a string to the file, creating or overwriting it.</ p >
< pre >< code > Path.new("output.txt").writeText("Hello, World!")</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > writeBytes</ span > (< span class = "param" > content</ span > )
</ div >
< p > Writes bytes to the file, creating or overwriting it.</ p >
< div class = "method-signature" >
< span class = "method-name" > touch</ span > ()
</ div >
< p > Creates an empty file if it does not exist, or updates its modification time if it does.</ p >
< pre >< code > Path.new("marker.txt").touch()</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > unlink</ span > ()
</ div >
< p > Deletes the file.</ p >
< div class = "method-signature" >
< span class = "method-name" > rename</ span > (< span class = "param" > target</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Moves or renames the file to < code > target</ code > . Returns the new Path.</ p >
< pre >< code > var old = Path.new("draft.txt")
var published = old.rename("article.txt")
System.print(published) // article.txt</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > replace</ span > (< span class = "param" > target</ span > ) → < span class = "type" > Path</ span >
</ div >
< p > Alias for < code > rename()</ code > . Replaces the target if it exists.</ p >
< div class = "method-signature" >
< span class = "method-name" > copyfile</ span > (< span class = "param" > dest</ span > )
</ div >
< p > Copies this file to < code > dest</ code > .</ p >
< pre >< code > Path.new("original.txt").copyfile("backup.txt")</ code ></ pre >
2026-01-25 13:09:28 +01:00
< div class = "method-signature" >
< span class = "method-name" > size</ span > () → < span class = "type" > Num</ span >
</ div >
< p > Returns the size of the file in bytes.</ p >
< pre >< code > var bytes = Path.new("data.txt").size()
System.print("File size: %(bytes) bytes")</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > mtime</ span > () → < span class = "type" > Num</ span >
</ div >
< p > Returns the modification time as a Unix timestamp (seconds since epoch).</ p >
< pre >< code > var modified = Path.new("file.txt").mtime()
System.print("Last modified: %(modified)")</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > atime</ span > () → < span class = "type" > Num</ span >
</ div >
< p > Returns the access time as a Unix timestamp.</ p >
< div class = "method-signature" >
< span class = "method-name" > ctime</ span > () → < span class = "type" > Num</ span >
</ div >
< p > Returns the change time (inode change) as a Unix timestamp.</ p >
2026-01-25 04:58:39 +01:00
< div class = "method-signature" >
< span class = "method-name" > chmod</ span > (< span class = "param" > mode</ span > )
</ div >
< p > Changes the file permissions. Mode is an octal number.</ p >
< pre >< code > Path.new("script.sh").chmod(0x1ed) // 0755 in octal</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > resolve</ span > () → < span class = "type" > Path</ span >
</ div >
< p > Returns the absolute real path, resolving any symlinks.</ p >
< pre >< code > var real = Path.new("./relative/../file.txt").resolve()
System.print(real) // /home/user/file.txt</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > samefile</ span > (< span class = "param" > other</ span > ) → < span class = "type" > Bool</ span >
</ div >
< p > Returns true if this path and < code > other</ code > refer to the same file (compares inodes).</ p >
< div class = "method-signature" >
< span class = "method-name" > owner</ span > () → < span class = "type" > String</ span >
</ div >
< p > Returns the username of the file owner (POSIX only).</ p >
< div class = "method-signature" >
< span class = "method-name" > group</ span > () → < span class = "type" > String</ span >
</ div >
< p > Returns the group name of the file (POSIX only).</ p >
< h2 id = "directory-operations" > Directory Operations</ h2 >
< div class = "method-signature" >
< span class = "method-name" > mkdir</ span > ()
</ div >
< p > Creates the directory.</ p >
< div class = "method-signature" >
< span class = "method-name" > mkdir</ span > (< span class = "param" > parents</ span > )
</ div >
< p > Creates the directory. If < code > parents</ code > is true, creates all intermediate directories.</ p >
< pre >< code > Path.new("/tmp/a/b/c").mkdir(true)</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > rmdir</ span > ()
</ div >
< p > Removes an empty directory.</ p >
< div class = "method-signature" >
< span class = "method-name" > rmtree</ span > ()
</ div >
< p > Recursively deletes the directory and all its contents.</ p >
< pre >< code > Path.new("/tmp/build_output").rmtree()</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > iterdir</ span > () → < span class = "type" > List</ span >
</ div >
< p > Returns a list of Path objects for each entry in the directory.</ p >
< pre >< code > for (entry in Path.new(".").iterdir()) {
System.print(entry.name)
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > glob</ span > (< span class = "param" > pattern</ span > ) → < span class = "type" > List</ span >
</ div >
< p > Returns paths matching the glob pattern in this directory. Supports < code > *</ code > and < code > ?</ code > wildcards.</ p >
< pre >< code > var txtFiles = Path.new(".").glob("*.txt")
for (f in txtFiles) {
System.print(f.name)
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > rglob</ span > (< span class = "param" > pattern</ span > ) → < span class = "type" > List</ span >
</ div >
< p > Recursively matches files in this directory and all subdirectories.</ p >
< pre >< code > var allWren = Path.cwd.rglob("*.wren")
System.print("Found %(allWren.count) Wren files")</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > walk</ span > () → < span class = "type" > List</ span >
</ div >
< p > Recursively walks the directory tree top-down. Returns a list of < code > [dirPath, dirNames, fileNames]</ code > tuples.</ p >
< pre >< code > for (entry in Path.new("src").walk()) {
var dir = entry[0]
var dirs = entry[1]
var files = entry[2]
for (f in files) {
System.print(dir / f)
}
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > walk</ span > (< span class = "param" > topDown</ span > ) → < span class = "type" > List</ span >
</ div >
< p > Walks the directory tree. If < code > topDown</ code > is false, visits child directories before parents.</ p >
< h2 id = "symlink-operations" > Symlink Operations</ h2 >
< div class = "method-signature" >
< span class = "method-name" > symlinkTo</ span > (< span class = "param" > target</ span > )
</ div >
< p > Creates a symbolic link at this path pointing to < code > target</ code > .</ p >
< pre >< code > Path.new("/tmp/link").symlinkTo("/etc/hosts")</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > hardlinkTo</ span > (< span class = "param" > target</ span > )
</ div >
< p > Creates a hard link at this path pointing to < code > target</ code > .</ p >
< div class = "method-signature" >
< span class = "method-name" > readlink</ span > () → < span class = "type" > String</ span >
</ div >
< p > Returns the target of the symbolic link.</ p >
< h2 id = "examples" > Examples</ h2 >
< h3 > Configuration File Management</ h3 >
< pre >< code > import "pathlib" for Path
import "json" for Json
var configDir = Path.home / ".config" / "myapp"
if (!configDir.exists()) {
configDir.mkdir(true)
}
var configFile = configDir / "settings.json"
if (!configFile.exists()) {
configFile.writeText(Json.stringify({
"theme": "dark",
"language": "en"
}, 2))
}
var settings = Json.parse(configFile.readText())
System.print("Theme: %(settings["theme"])")</ code ></ pre >
< h3 > Batch File Processing</ h3 >
< pre >< code > import "pathlib" for Path
var dataDir = Path.new("./data")
for (file in dataDir.glob("*.csv")) {
var size = file.stat().size
System.print("%(file.name): %(size) bytes")
}
var total = 0
for (wren in Path.cwd.rglob("*.wren")) {
total = total + 1
}
System.print("Found %(total) Wren source files")</ code ></ pre >
< h3 > Directory Tree Traversal</ h3 >
< pre >< code > import "pathlib" for Path
var src = Path.new("src")
for (entry in src.walk()) {
var dir = entry[0]
var files = entry[2]
if (files.count > 0) {
System.print("%(dir.relativeTo(src)):")
for (f in files) {
System.print(" %(f)")
}
}
}</ code ></ pre >
< h3 > Path Manipulation</ h3 >
< pre >< code > import "pathlib" for Path
var p = Path.new("/home/user/documents/report.tar.gz")
System.print(p.name) // report.tar.gz
System.print(p.stem) // report.tar
System.print(p.suffix) // .gz
System.print(p.suffixes) // [.tar, .gz]
System.print(p.parent) // /home/user/documents
var backup = p.withSuffix(".bak")
System.print(backup) // /home/user/documents/report.tar.bak
var renamed = p.withName("summary.pdf")
System.print(renamed) // /home/user/documents/summary.pdf</ code ></ pre >
< h3 > Temporary File Cleanup</ h3 >
< pre >< code > import "pathlib" for Path
var tmpDir = Path.new("/tmp/build_artifacts")
if (tmpDir.exists()) {
tmpDir.rmtree()
}
tmpDir.mkdir(true)
(tmpDir / "output.o").touch()
(tmpDir / "output.bin").touch()
var count = tmpDir.iterdir().count
System.print("%(count) files in build directory")
tmpDir.rmtree()</ code ></ pre >
< h3 > Home Directory Expansion</ h3 >
< pre >< code > import "pathlib" for Path
var shortcuts = [
Path.new("~/.bashrc"),
Path.new("~/.config"),
Path.new("~/Documents")
]
for (p in shortcuts) {
var expanded = p.expanduser()
System.print("%(p) -> %(expanded) (exists: %(expanded.exists()))")
}</ code ></ pre >
< div class = "admonition note" >
< div class = "admonition-title" > Note</ div >
< p > Path objects are immutable. Methods like < code > withName()</ code > , < code > withSuffix()</ code > , and < code > joinpath()</ code > return new Path objects rather than modifying the original.</ p >
</ div >
< div class = "admonition tip" >
< div class = "admonition-title" > Tip</ div >
< p > Use the < code > /</ code > operator for readable path construction: < code > Path.home / ".config" / "myapp" / "settings.json"</ code > reads naturally and handles separator insertion automatically.</ p >
</ div >
2026-01-26 05:12:14 +01:00
{% endblock %}