Skip to content

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

Draft
carpentry-agent[bot] wants to merge 1 commit into
masterfrom
claude/add-relative
Draft

Add Path.relative for lexical relative-path computation#9
carpentry-agent[bot] wants to merge 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`.
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.

0 participants