Skip to content

Add Path.relative for lexical relative-path computation - #9

Merged
hellerve merged 1 commit into
masterfrom
claude/add-relative
Aug 8, 2026
Merged

Add Path.relative for lexical relative-path computation#9
hellerve merged 1 commit into
masterfrom
claude/add-relative

Conversation

@carpentry-agent

Copy link
Copy Markdown

Adds Path.relative, the lexical companion to absolute: it computes the path to a target relative to a base without touching the filesystem — the staple operation behind Python's os.path.relpath, Rust's pathdiff, and Java's Path.relativize.

Behaviour

(relative target base) returns a (Maybe String):

call result
(relative "/a/b/c" "/a/b") Just "c"
(relative "/a/b" "/a/b/c") Just ".."
(relative "/a/x" "/a/b") Just "../x"
(relative "/a/b" "/a/b") Just "."
(relative "/a/b/c" "/x/y") Just "../../a/b/c"
(relative "/a/b" "a/b") Nothing (absolute vs relative)

It composes the existing primitives: both inputs run through normalize (so ./../redundant separators are resolved first), are split into components, stripped of the empty-string artifacts split yields at roots, then reduced to a common leading prefix plus one .. per leftover base component followed by the target's remainder. Nothing is returned when the paths cannot be related lexically — one absolute and one relative, or (on Windows) different drive roots.

Naming

The natural name would be relative-to, but core's Dynamic.carp already defines a defndynamic relative-to (the load-path helper behind add-c/add-cflags). That dynamic binding shadows the name for unqualified calls, so (relative-to …) after (use Path) expands the macro instead of calling the function and fails to type-check. Since the module is built for unqualified use (use-all Path throughout the tests), I named it relative, which also pairs cleanly with the existing absolute. Happy to switch to a qualified-only relative-to, or another name, if you'd rather.

Tests / CI

test/path.carp gains nine assert-equal cases covering every example above plus the relative→relative and reverse-mismatch cases. Local run: 56 passed, 0 failed. carp-fmt --check and angler are clean; committed docs regenerated via gendocs.carp.


Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

`relative` computes the path to a target relative to a base, purely lexically
(no filesystem access): it normalizes both inputs, finds their common leading
component prefix, then walks up with `..` for each remaining base component
before descending into the target's remainder. It returns a `(Maybe String)`:
`Nothing` when the paths cannot be related lexically (one absolute and one
relative, or different Windows drive roots) and `Just "."` for equal paths.

Named `relative` rather than `relative-to` because the core `defndynamic
relative-to` (the load-path helper in Dynamic.carp) shadows that name for
unqualified calls, which would break the module's `use`-based convention. It
pairs naturally with the existing `absolute`.
@hellerve
hellerve marked this pull request as ready for review August 8, 2026 15:58

@hellerve hellerve left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verified locally: 47/0 -> 56/0, and every row of the behaviour table reproduces exactly, including Nothing for absolute-vs-relative. the naming constraint is real (core Dynamic.carp:119 defines defndynamic relative-to); relative it is, pairing with absolute.

@hellerve
hellerve merged commit 5cd5027 into master Aug 8, 2026
2 checks passed
@hellerve
hellerve deleted the claude/add-relative branch August 8, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant