Module path

path
Stability: unstable — `Path` and the free `split_paths`/`join_paths` have Rust's names and meanings and are not expected to move. Two things keep it from freezing. `Path` is a `String` wrapper, so it is a BYTE path with no encoding guarantee, and on Windows that is not what the platform wants: the OS API is UTF-16, and the round trip through UTF-8 is lossy for names that are not valid Unicode. Rust answers this with `OsString`; Yo has not decided whether to, and that decision would change what `Path` holds. The other is that normalization is TEXTUAL — `normalize` resolves `.` and `..` lexically without touching the filesystem, which is the right default and matches Rust, but there is no `canonicalize` beside it that resolves symlinks. Adding one is additive; changing `normalize` to do it would not be. — stable modules only change additively; this one may still change.

Cross-platform filesystem path manipulation.

Stability

unstable — Path and the free split_paths/join_paths have Rust's names and meanings and are not expected to move. Two things keep it from freezing.

Path is a String wrapper, so it is a BYTE path with no encoding guarantee, and on Windows that is not what the platform wants: the OS API is UTF-16, and the round trip through UTF-8 is lossy for names that are not valid Unicode. Rust answers this with OsString; Yo has not decided whether to, and that decision would change what Path holds.

The other is that normalization is TEXTUAL — normalize resolves . and .. lexically without touching the filesystem, which is the right default and matches Rust, but there is no canonicalize beside it that resolves symlinks. Adding one is additive; changing normalize to do it would not be.

Types

Path object
Path

Filesystem path with segment-based operations.

Components are recorded as written. Empty components (repeated or trailing separators) and . components are dropped — dropping either can never change which file the path names. .. is kept: folding a/b/.. to a is only correct when b is not a symlink, so the fold is opt-in via normalize(). new, join and push therefore all agree — none of them folds. Use fs.canonicalize when the filesystem must be consulted.

_prefix holds a root marker that is not a bare /: a Windows drive (C:) or a UNC share (\\server\share). Both imply an absolute path and neither is ever popped by normalize() or rewritten by with_file_name / with_extension.

Fields

NameTypeDescription
_prefixString
_segmentsArrayList(String)
_is_absolutebool

Trait Implementations

impl(generic(T : Type), where(T <: ToString), T : (ToString))
impl(generic(T : Type), where(T <: ToString), T : (ToString), Format)
format : fn(self : Self, spec : str) -> String

Render self under spec. An unrecognised spec degrades to the plain to_string() rendering rather than failing.

Parameters

NameTypeNotes
selfSelf
specstr

Returns: String

impl(Path, ...)
new : (Path) fn(path_str : String) -> Path

Create a new path from a string. Normalizes separators, drops empty and . components, and records .. verbatim (see the type doc: folding .. is normalize()'s job, because it is wrong over symlinks).

Two leading BACKSLASHES are a Windows UNC share: the \\server\share root is kept whole in _prefix. //a/b is NOT a UNC path — on POSIX that is an ordinary root with a redundant separator.

Parameters

NameTypeNotes
path_strString

Returns: Path

from_cstr : (Path) fn(cstr : *(u8)) -> Path

Create a path from a C string pointer.

Parameters

NameTypeNotes
cstr*(u8)

Returns: Path

join : (Path) fn(generic(P) self : Path, other : P : (ToString)) -> Path

Join this path with another. If other is absolute, returns other. Join with any ToString — a str/String literal or another Path (audit path row: join(str)). Path round-trips through new losslessly (its rendering re-parses to the same components), so the generic facade is semantics-preserving for existing Path callers.

other's components are APPENDED, .. included: "a/b".join("../c") is a/b/../c, matching what Path.new("a/b/../c") records. Call normalize() on the result for the lexical fold.

Parameters

NameTypeNotes
selfPath
otherP : (ToString)

Returns: Path

_join_path : (Path) fn(self : Path, other : Path) -> Path

Parameters

NameTypeNotes
selfPath
otherPath

Returns: Path

normalize : (Path) fn(self : Path) -> Path

Fold .. LEXICALLY: each .. pops the component before it.

This is the OPT-IN half of the model new/join/push implement — it is not valid whenever a popped component is a symlink, because a/b/../c and a/c then name different files. Use fs.canonicalize (which asks the filesystem) when that matters; use this when a purely lexical answer is what you want, e.g. to key a cache on one spelling.

A leading .. on a RELATIVE path is PRESERVED — there is nothing before it to pop, and dropping it is what silently turned an escaping path into a non-escaping one. On an ABSOLUTE path it folds away, because /.. is /; a drive or UNC root (_prefix) is never popped either.

Parameters

NameTypeNotes
selfPath

Returns: Path

parent : (Path) fn(self : Path) -> Option(Path)

Get the parent directory path, or .None if there are no segments.

Parameters

NameTypeNotes
selfPath

Returns: Option(Path)

file_name : (Path) fn(self : Path) -> Option(String)

Get the last path segment (filename), or .None for empty paths.

Parameters

NameTypeNotes
selfPath

Returns: Option(String)

file_stem : (Path) fn(self : Path) -> Option(String)

Get the filename without its extension, or .None for empty paths.

Parameters

NameTypeNotes
selfPath

Returns: Option(String)

extension : (Path) fn(self : Path) -> Option(String)

Get the file extension (without the dot), or .None if there is none.

Parameters

NameTypeNotes
selfPath

Returns: Option(String)

with_extension : (Path) fn(self : Path, ext : String) -> Path

Return a new path with the extension replaced by ext.

Parameters

NameTypeNotes
selfPath
extString

Returns: Path

with_file_name : (Path) fn(self : Path, name : String) -> Path

Return a new path with the last segment replaced by name.

Parameters

NameTypeNotes
selfPath
nameString

Returns: Path

starts_with : (Path) fn(self : Path, base : Path) -> bool

Check if this path starts with the given base path (segment-wise).

Parameters

NameTypeNotes
selfPath
basePath

Returns: bool

ends_with : (Path) fn(self : Path, suffix : Path) -> bool

Check if this path ends with the given suffix path (segment-wise).

Parameters

NameTypeNotes
selfPath
suffixPath

Returns: bool

components : (Path) fn(self : Path) -> ArrayList(String)

Get all path components as a list. The first component is the ROOT when there is one: the drive / UNC share for a prefixed path, "/" for a plain absolute path, nothing for a relative one.

Parameters

NameTypeNotes
selfPath

Returns: ArrayList(String)

is_absolute : (Path) fn(self : Path) -> bool

Check if the path is absolute.

Parameters

NameTypeNotes
selfPath

Returns: bool

is_relative : (Path) fn(self : Path) -> bool

Check if the path is relative (not absolute).

Parameters

NameTypeNotes
selfPath

Returns: bool

strip_prefix : (Path) fn(self : Path, base : Path) -> Option(Path)

The remainder of self after base, as a relative path — Rust's Path::strip_prefix: .None unless base is a segment-wise prefix (starts_with); equal paths leave the empty relative path (renders as .). For "how do I get from base to self", .. segments and all, use relative_to — node's path.relative, which is what this method used to do (issues/fixed/path-strip-prefix-was-nodes-relative.md).

Parameters

NameTypeNotes
selfPath
basePath

Returns: Option(Path)

relative_to : (Path) fn(self : Path, base : Path) -> Path

Return self expressed relative to base: drop the segments the two share, emit one .. per remaining base segment, then the rest of self. Equivalent to node's path.relative(base, self). (This was strip_prefix until 2026-09-06; Rust's strip_prefix is the remainder-or-None above.)

Returns an empty relative path when the two are equal. When base and self differ in absoluteness there is no common root to walk from, so self is returned unchanged (node resolves both against the cwd first; this has no cwd to resolve against).

Parameters

NameTypeNotes
selfPath
basePath

Returns: Path

impl(Path, ToString(...))
to_string : ( self -> { segments := self._segments; result := String.new(); // A relative path with no segments is the CURRENT DIRECTORY and must // render as "." — `stat("")` is ENOENT, which made `yo test .` / // `yo check .` fail with a bare "file or directory not found". This // is a RENDER-level rule (Node: path.normalize(".") === "." and // path.dirname("foo") === "."): the empty-segment representation is // untouched, so join/parent/relative_from keep their semantics. if(!self._is_absolute && (segments.len() == usize(0)), { return(String.from(".")); }); // A UNC path has no forward-slash spelling worth emitting — its root // IS `\\server\share` — so it renders in the Windows spelling // throughout. Every other path keeps the module's `/` rendering (the // "Windows separator in to_string" audit item is unaffected). has_prefix := !self._prefix.is_empty(); is_unc := self._prefix.starts_with(String.from("\\")); sep := cond( is_unc => String.from("\\"), true => String.from("/") ); // A drive / UNC root IS the prefix; a plain absolute path's root is a // leading separator. cond( has_prefix => { result = result.concat(self._prefix); }, self._is_absolute => { result = result.concat(sep); }, true => () ); i := usize(0); segments_len := segments.len(); while(i < segments_len, i = (i + usize(1)), { seg := segments.get(i); match( seg, .Some(s) => { // The plain-absolute root already emitted its separator; every // other position needs one before the segment. cond( ((i > usize(0)) || has_prefix) => { result = result.concat(sep); }, true => () ); result = result.concat(s); }, .None => () ); }); return(result); } )
impl(Path, Eq(Path))
impl(Path, Clone(...))
clone : (Path) fn(self : Path) -> Path

Create an independent clone of self.

Parameters

NameTypeNotes
selfPath

Returns: Path

impl(Path, Hash(...))
hash : (Path) fn(generic(H) self : Path, hasher : H : (Hasher)) -> unit

Feed this value's identity into hasher.

Parameters

NameTypeNotes
selfPath
hasherH : (Hasher)

Returns: unit

impl(Path, Ord(Path)(...))
impl(Path, ...)
push : (Path) fn(generic(P) self : Path, other : P : (ToString)) -> unit

Parameters

NameTypeNotes
selfPath
otherP : (ToString)

Returns: unit

impl(Path, ...)
ancestors : (Path) fn(self : Path) -> ArrayList(Path)

Parameters

NameTypeNotes
selfPath

Returns: ArrayList(Path)

impl(Path, Default(...))
default : (Path) fn() -> Path

The default value of the type.

Returns: Path

Methods
to_string : (Path) fn(self : Path) -> String

Render self as the text a USER should read — Rust's Display::fmt, not its Debug. Hand-written (or generated by derive(Error) from a per-variant format string) whenever the structural form would be wrong.

Parameters

NameTypeNotes
selfPath

Returns: String

== : (Path) fn(lhs : Path, rhs : Path) -> bool

Parameters

NameTypeNotes
lhsPath
rhsPath

Returns: bool

!= : (Path) fn(lhs : Path, rhs : Path) -> bool

Parameters

NameTypeNotes
lhsPath
rhsPath

Returns: bool

< : (Path) fn(lhs : Path, rhs : Path) -> bool

Parameters

NameTypeNotes
lhsPath
rhsPath

Returns: bool

<= : (Path) fn(lhs : Path, rhs : Path) -> bool

Parameters

NameTypeNotes
lhsPath
rhsPath

Returns: bool

> : (Path) fn(lhs : Path, rhs : Path) -> bool

Parameters

NameTypeNotes
lhsPath
rhsPath

Returns: bool

>= : (Path) fn(lhs : Path, rhs : Path) -> bool

Parameters

NameTypeNotes
lhsPath
rhsPath

Returns: bool

cmp : (Path) fn(lhs : Path, rhs : Path) -> Ordering

Parameters

NameTypeNotes
lhsPath
rhsPath

Returns: Ordering

Functions

split_paths function
fn(s : String) -> ArrayList(Path)

Split a PATH-style string ("/usr/bin:/bin" on Unix, ;-separated on Windows) into its Path components. Empty segments are dropped.

Parameters

NameTypeNotes
sString

Returns: ArrayList(Path)

join_paths function
fn(paths : ArrayList(Path)) -> String

Join paths back into a PATH-style string with the platform delimiter.

Parameters

NameTypeNotes
pathsArrayList(Path)

Returns: String

Constants

PATH_SEPARATOR constant u8

Platform-specific path separator: '/' on Unix, '\\' on Windows.

Value: 47

PATH_DELIMITER constant u8

Platform-specific path list delimiter: ':' on Unix, ';' on Windows.

Value: 58