Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ is a simple file path library for Carp.
The `Path` module mostly operates on `String` arguments. It allows you to
split, join, merge, and normalize paths and extensions in a lot of different
ways. `normalize` resolves `.`/`..` segments and collapses repeated separators
lexically (without touching the filesystem). It also has some functions to work
with the `PATH` environment variable.
lexically (without touching the filesystem), and `relative` expresses one path
relative to a base the same way. It also has some functions to work with the
`PATH` environment variable.

It assumes either Windows or POSIX-style separators.

Expand Down
25 changes: 25 additions & 0 deletions docs/Path.html
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,31 @@ <h3 id="path-max">

</p>
</div>
<div class="binder">
<a class="anchor" href="#relative">
<h3 id="relative">
relative
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(Fn [(Ref String a), (Ref String b)] (Maybe String))
</p>
<pre class="args">
(relative target base)
</pre>
<p class="doc">
<p>computes the path to <code>target</code> relative to <code>base</code>, purely
lexically. It is the companion to <a href="#absolute">absolute</a>.</p>
<p>Both paths are normalized first, so <code>.</code>, <code>..</code>, and redundant separators are
resolved before comparing. Returns <code>Nothing</code> when the paths cannot be related
lexically: one absolute and one relative, or, on Windows, rooted at different
drives. Equal paths yield <code>Just &quot;.&quot;</code>.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#relative?">
<h3 id="relative?">
Expand Down
25 changes: 25 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,31 @@ <h3 id="path-max">

</p>
</div>
<div class="binder">
<a class="anchor" href="#relative">
<h3 id="relative">
relative
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(Fn [(Ref String a), (Ref String b)] (Maybe String))
</p>
<pre class="args">
(relative target base)
</pre>
<p class="doc">
<p>computes the path to <code>target</code> relative to <code>base</code>, purely
lexically. It is the companion to <a href="#absolute">absolute</a>.</p>
<p>Both paths are normalized first, so <code>.</code>, <code>..</code>, and redundant separators are
resolved before comparing. Returns <code>Nothing</code> when the paths cannot be related
lexically: one absolute and one relative, or, on Windows, rooted at different
drives. Equal paths yield <code>Just &quot;.&quot;</code>.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#relative?">
<h3 id="relative?">
Expand Down
34 changes: 34 additions & 0 deletions path.carp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,40 @@ an extension if there previously was none.")
(Maybe.Just @p)
(Maybe.apply (cwd) &(fn [d] (join &[d @p])))))

(doc relative "computes the path to `target` relative to `base`, purely
lexically. It is the companion to [absolute](#absolute).

Both paths are normalized first, so `.`, `..`, and redundant separators are
resolved before comparing. Returns `Nothing` when the paths cannot be related
lexically: one absolute and one relative, or, on Windows, rooted at different
drives. Equal paths yield `Just \".\"`.")
(defn relative [target base]
(let [nt (normalize target)
nb (normalize base)
abs (absolute? &nt)]
(if (/= abs (absolute? &nb))
(Maybe.Nothing)
(let [tc (split &nt)
bc (split &nb)]
(if (and abs (/= (Array.unsafe-nth &tc 0) (Array.unsafe-nth &bc 0)))
(Maybe.Nothing)
(let [non-empty? (fn [s] (not (String.empty? s)))
tf (Array.copy-filter &non-empty? &tc)
bf (Array.copy-filter &non-empty? &bc)
tn (Array.length &tf)
bn (Array.length &bf)
common (let-do [k 0]
(while (and (< k tn)
(< k bn)
(= (Array.unsafe-nth &tf k)
(Array.unsafe-nth &bf k)))
(set! k (inc k)))
k)
ups (Array.replicate (- bn common) "..")
comps (Array.concat &[ups (Array.suffix &tf common)])
joined (join &comps)]
(Maybe.Just (if (String.empty? &joined) @"." joined))))))))

(doc split-search-path "splits a `PATH` environment variable `p`.")
(defn split-search-path [p] (String.split-by p &[search-path-separator]))
(doc get-search-path "gets the `PATH` environment variable and splits it.")
Expand Down
36 changes: 36 additions & 0 deletions test/path.carp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,42 @@
"a"
&(normalize "a/")
"normalize strips a trailing separator")
(assert-equal test
&(Maybe.Just @"c")
&(relative "/a/b/c" "/a/b")
"relative descends into a subdirectory")
(assert-equal test
&(Maybe.Just @"..")
&(relative "/a/b" "/a/b/c")
"relative walks up with ..")
(assert-equal test
&(Maybe.Just @"../x")
&(relative "/a/x" "/a/b")
"relative walks up and back down")
(assert-equal test
&(Maybe.Just @".")
&(relative "/a/b" "/a/b")
"relative of equal paths is .")
(assert-equal test
&(Maybe.Just @"../../a/b/c")
&(relative "/a/b/c" "/x/y")
"relative with no shared components")
(assert-equal test
&(Maybe.Just @"c")
&(relative "/a/./b/../c" "/a")
"relative normalizes both paths first")
(assert-equal test
&(Maybe.Just @"c")
&(relative "a/b/c" "a/b")
"relative works on relative paths")
(assert-equal test
&(Maybe.Nothing)
&(relative "/a/b" "a/b")
"relative returns Nothing for an absolute target and relative base")
(assert-equal test
&(Maybe.Nothing)
&(relative "a/b" "/a/b")
"relative returns Nothing for a relative target and absolute base")
(assert-equal test
&(Maybe.Just @"file.txt")
&(filename "path/to/file.txt")
Expand Down