This commit is contained in:
2020-01-25 16:16:23 +01:00
commit 81b3cafbdd
6 changed files with 946 additions and 0 deletions

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
# path
is a simple file path library for Carp.
## Installation
```clojure
(load "https://veitheller.de/git/carpentry/path@0.0.1")
```
### Usage
The `Path` module mostly operates on `String` arguments. It allows you to
split, join, and merge paths and extensions in a lot of different ways. It also
has some functions to work with the `PATH` environment variable.
It assumes either Windows or POSIX-style separators.
Look at [the documentation](https://veitheller.de/path) for more information.
<hr/>
Have fun!

551
docs/Path.html Normal file
View File

@@ -0,0 +1,551 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="content">
<div class="logo">
<a href="http://github.com/carp-lang/Carp">
<img src="logo.png">
</a>
<div class="title">
path
</div>
<div class="index">
<ul>
<li>
<a href="Path.html">
Path
</a>
</li>
</ul>
</div>
</div>
<h1>
Path
</h1>
<div class="module-description">
<p>is a simple file path library for Carp.</p>
<h2>Installation</h2>
<pre><code class="language-clojure">(load &quot;https://veitheller.de/git/carpentry/path@0.0.1&quot;)
</code></pre>
<h3>Usage</h3>
<p>The <code>Path</code> module mostly operates on <code>String</code> arguments. It allows you to
split, join, and merge paths and extensions in a lot of different ways. It also
has some functions to work with the <code>PATH</code> environment variable.</p>
<p>It assumes either Windows or POSIX-style separators.</p>
</div>
<div class="binder">
<a class="anchor" href="#&lt;/&gt;">
<h3 id="&lt;/&gt;">
&lt;/&gt;
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [a, b] String)
</p>
<pre class="args">
(&lt;/&gt; before after)
</pre>
<p class="doc">
<p>joins <code>before</code> and <code>after</code> using the default path separator.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#absolute">
<h3 id="absolute">
absolute
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] (Maybe String))
</p>
<pre class="args">
(absolute p)
</pre>
<p class="doc">
<p>makes an absolute path from <code>p</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#absolute?">
<h3 id="absolute?">
absolute?
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] Bool)
</p>
<pre class="args">
(absolute? p)
</pre>
<p class="doc">
<p>checks whether a path is absolute.</p>
<p>As such, it is the inverse to <a href="#relative">relative</a>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#add-extension">
<h3 id="add-extension">
add-extension
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String, &amp;String] String)
</p>
<pre class="args">
(add-extension p ext)
</pre>
<p class="doc">
<p>adds an extension <code>ext</code> to a path <code>p</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#basename">
<h3 id="basename">
basename
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] String)
</p>
<pre class="args">
(basename p)
</pre>
<p class="doc">
<p>gets the basename of the path <code>p</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#cwd">
<h3 id="cwd">
cwd
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [] (Maybe String))
</p>
<pre class="args">
(cwd)
</pre>
<p class="doc">
<p>returns the current working directory as a <code>Maybe</code>. The ways in
which it can fail are OS-dependent, but it should happen relatively rare.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#drop-extension">
<h3 id="drop-extension">
drop-extension
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] String)
</p>
<pre class="args">
(drop-extension p)
</pre>
<p class="doc">
<p>drops the extension of a path <code>p</code>. Does nothing if there
is none.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#extension">
<h3 id="extension">
extension
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] (Maybe String))
</p>
<pre class="args">
(extension p)
</pre>
<p class="doc">
<p>gets the extension of a file as a <code>Maybe</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#filename">
<h3 id="filename">
filename
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] (Maybe String))
</p>
<pre class="args">
(filename p)
</pre>
<p class="doc">
<p>gets the filename of the path <code>p</code> as a <code>(Maybe String)</code>.</p>
<p>It will return <code>Nothing</code> if an empty string is passed.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#get-search-path">
<h3 id="get-search-path">
get-search-path
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [] (Maybe (Array String)))
</p>
<pre class="args">
(get-search-path)
</pre>
<p class="doc">
<p>gets the <code>PATH</code> environment variable and splits it.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#has-extension?">
<h3 id="has-extension?">
has-extension?
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] Bool)
</p>
<pre class="args">
(has-extension? p)
</pre>
<p class="doc">
<p>cheks whether the path <code>p</code> has an extension.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#is-extension?">
<h3 id="is-extension?">
is-extension?
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String, &amp;String] Bool)
</p>
<pre class="args">
(is-extension? p ext)
</pre>
<p class="doc">
<p>checks whether the path <code>p</code> has the extension <code>ext</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#join">
<h3 id="join">
join
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [(Ref (Array String))] String)
</p>
<pre class="args">
(join ps)
</pre>
<p class="doc">
<p>joins the path components <code>ps</code> into a path.</p>
<p>As such, it is the inverse to <a href="#split">split</a>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#path-max">
<h3 id="path-max">
path-max
</h3>
</a>
<div class="description">
external
</div>
<p class="sig">
Int
</p>
<span>
</span>
<p class="doc">
<p>defines the maximum path length on this OS.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#relative?">
<h3 id="relative?">
relative?
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] Bool)
</p>
<pre class="args">
(relative? p)
</pre>
<p class="doc">
<p>checks whether a path is relative.</p>
<p>As such, it is the inverse to <a href="#absolute">absolute</a>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#replace-extension">
<h3 id="replace-extension">
replace-extension
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String, &amp;String] String)
</p>
<pre class="args">
(replace-extension p ext)
</pre>
<p class="doc">
<p>replaces the extension of a path <code>p</code> with <code>ext</code>. Adds
an extension if there previously was none.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#search-path-separator">
<h3 id="search-path-separator">
search-path-separator
</h3>
</a>
<div class="description">
def
</div>
<p class="sig">
Char
</p>
<span>
</span>
<p class="doc">
<p>is the separator for the <code>PATH</code> environment
variable we use on this OS.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#search-path-separator?">
<h3 id="search-path-separator?">
search-path-separator?
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [Char] Bool)
</p>
<pre class="args">
(search-path-separator? c)
</pre>
<p class="doc">
</p>
</div>
<div class="binder">
<a class="anchor" href="#separator">
<h3 id="separator">
separator
</h3>
</a>
<div class="description">
def
</div>
<p class="sig">
Char
</p>
<span>
</span>
<p class="doc">
<p>is the default separator we use on this OS.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#separator?">
<h3 id="separator?">
separator?
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;Char] Bool)
</p>
<pre class="args">
(separator? c)
</pre>
<p class="doc">
<p>checks whether the character <code>c</code> is a separator for the
<code>PATH</code> environment variable on this OS.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#separators">
<h3 id="separators">
separators
</h3>
</a>
<div class="description">
def
</div>
<p class="sig">
(Array Char)
</p>
<span>
</span>
<p class="doc">
<p>is the possible separators we could use on this OS.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#split">
<h3 id="split">
split
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] (Array String))
</p>
<pre class="args">
(split p)
</pre>
<p class="doc">
<p>splits the path <code>p</code> into its components.</p>
<p>As such, it is the inverse to <a href="#join">join</a>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#split-extension">
<h3 id="split-extension">
split-extension
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] (Maybe (Pair String String)))
</p>
<pre class="args">
(split-extension p)
</pre>
<p class="doc">
<p>splits the path <code>p</code> on its extension.</p>
<p>It will return a <code>(Maybe (Pair String String))</code>. <code>Maybe</code> because there might not
be an extension, and <code>Pair</code> because it will return the part before and after
the extension.</p>
<p>Examples on POSIX:</p>
<pre><code>(split-extension &quot;file.txt&quot;)
; =&gt; (Maybe.Just (Pair &quot;file&quot; &quot;txt&quot;))
(split-extension &quot;file&quot;)
; =&gt; (Maybe.Nothing)
(split-extension &quot;file/file.txt&quot;)
; =&gt; (Maybe.Just (Pair &quot;file/file&quot; &quot;txt&quot;))
(split-extension &quot;file.txt/veit&quot;)
; =&gt; (Maybe.Nothing)
(split-extension &quot;file.txt/veit.ext&quot;)
; =&gt; (Maybe.Just (Pair &quot;file.txt/veit&quot; &quot;ext&quot;))
(split-extension &quot;file/path.txt.bob.fred&quot;)
; =&gt; (Maybe.Just (Pair &quot;file/path.txt.bob&quot; &quot;fred&quot;))
</code></pre>
</p>
</div>
<div class="binder">
<a class="anchor" href="#split-search-path">
<h3 id="split-search-path">
split-search-path
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(λ [&amp;String] (Array String))
</p>
<pre class="args">
(split-search-path p)
</pre>
<p class="doc">
<p>splits a <code>PATH</code> environment variable <code>p</code>.</p>
</p>
</div>
</div>
</body>
</html>

110
docs/style.css Normal file
View File

@@ -0,0 +1,110 @@
html {
font-family: "Helvetica", sans-serif;
font-size: 16px;
}
a {
color: #000;
}
.logo {
display: none;
}
ul {
list-style-type: none;
font-family: "Hasklig", "Lucida Console", monospace;
line-height: 1.4em;
}
.module-description {
margin-bottom: 3em;
}
.content {
margin: 3em auto auto auto;
width: 80%;
max-width: 610px;
min-width: 400px
}
h1 {
margin-bottom: 1em;
font-weight: 400;
}
h2 {
font-weight: 400;
margin-bottom: 0em;
}
h3 {
margin: 0em;
font-weight: 400;
}
.binder {
margin: 0em 0em 3.5em 0em;
}
.sig {
font-family: "Hasklig", "Lucida Console", monospace;
margin: 0.5em 0em 0.5em 0em;
}
.args {
background-color: #eee;
display: inline-block;
white-space: normal;
margin: 0;
margin-bottom: 1em;
}
code {
background-color: #eee;
}
pre {
background-color: #eee;
overflow-y: scroll;
}
.description {
margin-top: 0.3em;
font-size: 0.8em;
color: #aaa;
}
.huge {
font-size: 15em;
margin: 0em;
}
/* Smaller screens */
@media only screen and (max-width: 600px) {
.logo {
margin: 1em;
text-align: left;
float: left;
width: 100%;
}
.logo img {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
}
.content {
margin: 0.5em;
}
.binder {
margin: 0em 0em 1.5em 0em;
}
.sig {
font-size: 0.9em;
}
ul {
padding: 0px;
}
}
.title, .index { display: none; }

13
gendocs.carp Normal file
View File

@@ -0,0 +1,13 @@
(load "path.carp")
(defndynamic gendocs []
(do
(Project.config "title" "path")
(Project.config "docs-directory" "./docs/")
(Project.config "docs-logo" "")
(Project.config "docs-styling" "style.css")
(Project.config "docs-generate-index" false)
(save-docs Path)))
(gendocs)
(quit)

152
path.carp Normal file
View File

@@ -0,0 +1,152 @@
(doc Path "is a simple file path library for Carp.
## Installation
```clojure
(load \"https://veitheller.de/git/carpentry/path@0.0.1\")
```
### Usage
The `Path` module mostly operates on `String` arguments. It allows you to
split, join, and merge paths and extensions in a lot of different ways. It also
has some functions to work with the `PATH` environment variable.
It assumes either Windows or POSIX-style separators.")
(defmodule Path
(doc absolute? "checks whether a path is absolute.
As such, it is the inverse to [relative](#relative).")
(doc separator "is the default separator we use on this OS.")
(doc separators "is the possible separators we could use on this OS.")
(doc search-path-separator "is the separator for the `PATH` environment
variable we use on this OS.")
(private extension-pat)
(hidden extension-pat)
(private sep-string)
(hidden sep-string)
(windows-only
(defn absolute? [p] (Pattern.matches? #"[A-Za-z]:\\" p))
(def separator \\)
(def separators [\/ \\])
(def search-path-separator \;)
(def extension-pat #"\.[^\\/\.]*$")
(def sep-string "\\"))
(not-on-windows
(defn absolute? [p] (String.starts-with? p "/"))
(def separator \/)
(def separators [\/])
(def search-path-separator \:)
(def extension-pat #"\.[^/\.]*$")
(def sep-string "/"))
(doc relative? "checks whether a path is relative.
As such, it is the inverse to [absolute](#absolute).")
(defn relative? [p] (not (absolute? p)))
(doc separator? "checks whether the character `c` is a path separator on this
OS.")
(defn separator? [c] (Array.contains? &separators c))
(doc separator? "checks whether the character `c` is a separator for the
`PATH` environment variable on this OS.")
(defn search-path-separator? [c] (= search-path-separator c))
(doc path-max "defines the maximum path length on this OS.")
(register path-max Int "PATH_MAX")
(private cwd-)
(hidden cwd-)
(register cwd- (Fn [String Int] String) "getcwd")
(doc cwd "returns the current working directory as a `Maybe`. The ways in
which it can fail are OS-dependent, but it should happen relatively rare.")
(defn cwd []
(let [s (String.allocate path-max (Char.from-int 0))
r (cwd- s path-max)]
(if (null? (cstr &r))
(Maybe.Nothing)
(Maybe.Just r))))
(doc </> "joins `before` and `after` using the default path separator.")
(defn </> [before after] (fmt "%s%c%s" before separator after))
(doc split "splits the path `p` into its components.
As such, it is the inverse to [join](#join).")
(defn split [p] (String.split-by p &[separator]))
(doc join "joins the path components `ps` into a path.
As such, it is the inverse to [split](#split).")
(defn join [ps] (String.join sep-string ps))
(doc filename "gets the filename of the path `p` as a `(Maybe String)`.
It will return `Nothing` if an empty string is passed.")
(defn filename [p] (Array.last &(split p)))
(doc basename "gets the basename of the path `p`.")
(defn basename [p]
(let [split (split p)
but-last (Array.prefix-array &split (dec (Array.length &split)))]
(String.join sep-string &but-last)))
(doc split-extension "splits the path `p` on its extension.
It will return a `(Maybe (Pair String String))`. `Maybe` because there might not
be an extension, and `Pair` because it will return the part before and after
the extension.
Examples on POSIX:
```
(split-extension \"file.txt\")
; => (Maybe.Just (Pair \"file\" \"txt\"))
(split-extension \"file\")
; => (Maybe.Nothing)
(split-extension \"file/file.txt\")
; => (Maybe.Just (Pair \"file/file\" \"txt\"))
(split-extension \"file.txt/veit\")
; => (Maybe.Nothing)
(split-extension \"file.txt/veit.ext\")
; => (Maybe.Just (Pair \"file.txt/veit\" \"ext\"))
(split-extension \"file/path.txt.bob.fred\")
; => (Maybe.Just (Pair \"file/path.txt.bob\" \"fred\"))
```")
(defn split-extension [p]
(let [i (Pattern.find extension-pat p)]
(if (= -1 i)
(Maybe.Nothing)
(Maybe.Just (Pair.init (prefix-string p i) (suffix-string p (inc i)))))))
(doc extension "gets the extension of a file as a `Maybe`.")
(defn extension [p]
(Maybe.apply (split-extension p) &(fn [p] @(Pair.b &p))))
(doc has-extension? "cheks whether the path `p` has an extension.")
(defn has-extension? [p] (Maybe.just? &(split-extension p)))
(doc is-extension? "checks whether the path `p` has the extension `ext`.")
(defn is-extension? [p ext] (= &(extension p) &(Maybe.Just @ext)))
(doc drop-extension "drops the extension of a path `p`. Does nothing if there
is none.")
(defn drop-extension [p]
@(match (split-extension p)
(Maybe.Nothing) p
(Maybe.Just pair) (Pair.a &pair)))
(doc add-extension "adds an extension `ext` to a path `p`.")
(defn add-extension [p ext] (String.concat &[@p @"." @ext]))
(doc replace-extension "replaces the extension of a path `p` with `ext`. Adds
an extension if there previously was none.")
(defn replace-extension [p ext] (add-extension &(drop-extension p) ext))
(doc absolute "makes an absolute path from `p`.")
(defn absolute [p]
(if (absolute? p)
(Maybe.Just @p)
(Maybe.apply (cwd) &(fn [d] (join &[d @p])))))
(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.")
(defn get-search-path []
(Maybe.apply (IO.getenv @"PATH") &(fn [p] (split-search-path &p))))
)

97
test/path.carp Normal file
View File

@@ -0,0 +1,97 @@
(load "Test.carp")
(load "path.carp")
(use-all Path Test)
(if (not (Dynamic.or (= "windows" (os)) (= "mingw32" (os))))
(deftest test
(assert-equal test
"path/joined"
&(</> "path" "joined")
"</> works on paths"
)
(assert-true test
(Maybe.just? &(absolute "path"))
"absolute works"
)
(assert-false test
(absolute? "path")
"absolute? works on relative paths"
)
(assert-false test
(relative? "/path")
"relative? works on absolute paths"
)
(assert-true test
(relative? "path")
"relative? works on relative paths"
)
(assert-equal test
"file.ext"
&(add-extension "file" "ext")
"add-extension works"
)
(assert-equal test
"/path"
&(basename "/path/file.txt")
"basename works"
)
(assert-true test
(Maybe.just? &(cwd))
"cwd works"
)
; TODO why does this test not work?
;(assert-equal test
; "file"
; &(drop-extension "file.txt")
; "drop-extension works if there is an extension"
;)
(assert-equal test
"file"
&(drop-extension "file")
"drop-extension works if there is no extension"
)
(assert-equal test
&(Maybe.Just @"txt")
&(extension "file.txt")
"extension works if there is an extension"
)
(assert-equal test
&(Maybe.Nothing)
&(extension "file")
"extension works if there is no extension"
)
(assert-true test
(Maybe.just? &(get-search-path))
"get-search-path works"
)
(assert-true test
(has-extension? "file.txt")
"has-extension? works if there is an extension"
)
(assert-false test
(has-extension? "file")
"has-extension? works if there is no extension"
)
(assert-true test
(is-extension? "file.txt" "txt")
"is-extension? works if there is the right extension"
)
(assert-false test
(is-extension? "file.txt" "ext")
"is-extension? works if there is the wrong extension"
)
(assert-false test
(is-extension? "file" "txt")
"is-extension? works if there is no extension"
)
)
())
(if (Dynamic.or (= "windows" (os)) (= "mingw32" (os)))
(deftest test
(assert-true test
false
"tests are not currently implemented on windows"
)
)
())