Module path
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
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
| Name | Type | Description |
|---|---|---|
_prefix | String | |
_segments | ArrayList(String) | |
_is_absolute | bool |
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) -> StringRender self under spec. An unrecognised spec degrades to the plain
to_string() rendering rather than failing.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Self | |
spec | str |
Returns: String
impl(Path, ...)
new : (Path) fn(path_str : String) -> PathCreate 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
| Name | Type | Notes |
|---|---|---|
path_str | String |
Returns: Path
from_cstr : (Path) fn(cstr : *(u8)) -> Pathjoin : (Path) fn(generic(P) self : Path, other : P : (ToString)) -> PathJoin 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
| Name | Type | Notes |
|---|---|---|
self | Path | |
other | P : (ToString) |
Returns: Path
_join_path : (Path) fn(self : Path, other : Path) -> Pathnormalize : (Path) fn(self : Path) -> PathFold .. 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
| Name | Type | Notes |
|---|---|---|
self | Path |
Returns: Path
parent : (Path) fn(self : Path) -> Option(Path)file_name : (Path) fn(self : Path) -> Option(String)file_stem : (Path) fn(self : Path) -> Option(String)extension : (Path) fn(self : Path) -> Option(String)with_extension : (Path) fn(self : Path, ext : String) -> Pathwith_file_name : (Path) fn(self : Path, name : String) -> Pathstarts_with : (Path) fn(self : Path, base : Path) -> boolends_with : (Path) fn(self : Path, suffix : Path) -> boolcomponents : (Path) fn(self : Path) -> ArrayList(String)is_absolute : (Path) fn(self : Path) -> boolis_relative : (Path) fn(self : Path) -> boolstrip_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
| Name | Type | Notes |
|---|---|---|
self | Path | |
base | Path |
relative_to : (Path) fn(self : Path, base : Path) -> PathReturn 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
| Name | Type | Notes |
|---|---|---|
self | Path | |
base | Path |
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(...))
impl(Path, Hash(...))
impl(Path, Ord(Path)(...))
impl(Path, ...)
impl(Path, ...)
impl(Path, Default(...))
default : (Path) fn() -> PathThe default value of the type.
Returns: Path
Methods
to_string : (Path) fn(self : Path) -> String== : (Path) fn(lhs : Path, rhs : Path) -> bool!= : (Path) fn(lhs : Path, rhs : Path) -> bool< : (Path) fn(lhs : Path, rhs : Path) -> bool<= : (Path) fn(lhs : Path, rhs : Path) -> bool> : (Path) fn(lhs : Path, rhs : Path) -> bool>= : (Path) fn(lhs : Path, rhs : Path) -> boolFunctions
Constants
Platform-specific path separator: '/' on Unix, '\\' on Windows.
Value: 47
Platform-specific path list delimiter: ':' on Unix, ';' on Windows.
Value: 58