From 81b3cafbdd64b51b71e595c9b77dcfeeec0591f9 Mon Sep 17 00:00:00 2001 From: hellerve Date: Sat, 25 Jan 2020 16:16:23 +0100 Subject: [PATCH] initial --- README.md | 23 +++ docs/Path.html | 551 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/style.css | 110 ++++++++++ gendocs.carp | 13 ++ path.carp | 152 ++++++++++++++ test/path.carp | 97 +++++++++ 6 files changed, 946 insertions(+) create mode 100644 README.md create mode 100644 docs/Path.html create mode 100644 docs/style.css create mode 100644 gendocs.carp create mode 100644 path.carp create mode 100644 test/path.carp diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca887f1 --- /dev/null +++ b/README.md @@ -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. + +
+ +Have fun! diff --git a/docs/Path.html b/docs/Path.html new file mode 100644 index 0000000..63fa65b --- /dev/null +++ b/docs/Path.html @@ -0,0 +1,551 @@ + + + + + + + + + +
+ +

+ Path +

+
+

is a simple file path library for Carp.

+

Installation

+
(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.

+ +
+
+ +

+ </> +

+
+
+ defn +
+

+ (λ [a, b] String) +

+
+                    (</> before after)
+                
+

+

joins before and after using the default path separator.

+ +

+
+
+ +

+ absolute +

+
+
+ defn +
+

+ (λ [&String] (Maybe String)) +

+
+                    (absolute p)
+                
+

+

makes an absolute path from p.

+ +

+
+
+ +

+ absolute? +

+
+
+ defn +
+

+ (λ [&String] Bool) +

+
+                    (absolute? p)
+                
+

+

checks whether a path is absolute.

+

As such, it is the inverse to relative.

+ +

+
+
+ +

+ add-extension +

+
+
+ defn +
+

+ (λ [&String, &String] String) +

+
+                    (add-extension p ext)
+                
+

+

adds an extension ext to a path p.

+ +

+
+
+ +

+ basename +

+
+
+ defn +
+

+ (λ [&String] String) +

+
+                    (basename p)
+                
+

+

gets the basename of the path p.

+ +

+
+
+ +

+ cwd +

+
+
+ defn +
+

+ (λ [] (Maybe String)) +

+
+                    (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.

+ +

+
+
+ +

+ drop-extension +

+
+
+ defn +
+

+ (λ [&String] String) +

+
+                    (drop-extension p)
+                
+

+

drops the extension of a path p. Does nothing if there +is none.

+ +

+
+
+ +

+ extension +

+
+
+ defn +
+

+ (λ [&String] (Maybe String)) +

+
+                    (extension p)
+                
+

+

gets the extension of a file as a Maybe.

+ +

+
+
+ +

+ filename +

+
+
+ defn +
+

+ (λ [&String] (Maybe String)) +

+
+                    (filename p)
+                
+

+

gets the filename of the path p as a (Maybe String).

+

It will return Nothing if an empty string is passed.

+ +

+
+
+ +

+ get-search-path +

+
+
+ defn +
+

+ (λ [] (Maybe (Array String))) +

+
+                    (get-search-path)
+                
+

+

gets the PATH environment variable and splits it.

+ +

+
+
+ +

+ has-extension? +

+
+
+ defn +
+

+ (λ [&String] Bool) +

+
+                    (has-extension? p)
+                
+

+

cheks whether the path p has an extension.

+ +

+
+
+ +

+ is-extension? +

+
+
+ defn +
+

+ (λ [&String, &String] Bool) +

+
+                    (is-extension? p ext)
+                
+

+

checks whether the path p has the extension ext.

+ +

+
+
+ +

+ join +

+
+
+ defn +
+

+ (λ [(Ref (Array String))] String) +

+
+                    (join ps)
+                
+

+

joins the path components ps into a path.

+

As such, it is the inverse to split.

+ +

+
+
+ +

+ path-max +

+
+
+ external +
+

+ Int +

+ + + +

+

defines the maximum path length on this OS.

+ +

+
+
+ +

+ relative? +

+
+
+ defn +
+

+ (λ [&String] Bool) +

+
+                    (relative? p)
+                
+

+

checks whether a path is relative.

+

As such, it is the inverse to absolute.

+ +

+
+
+ +

+ replace-extension +

+
+
+ defn +
+

+ (λ [&String, &String] String) +

+
+                    (replace-extension p ext)
+                
+

+

replaces the extension of a path p with ext. Adds +an extension if there previously was none.

+ +

+
+
+ +

+ search-path-separator +

+
+
+ def +
+

+ Char +

+ + + +

+

is the separator for the PATH environment +variable we use on this OS.

+ +

+
+
+ +

+ search-path-separator? +

+
+
+ defn +
+

+ (λ [Char] Bool) +

+
+                    (search-path-separator? c)
+                
+

+ +

+
+
+ +

+ separator +

+
+
+ def +
+

+ Char +

+ + + +

+

is the default separator we use on this OS.

+ +

+
+
+ +

+ separator? +

+
+
+ defn +
+

+ (λ [&Char] Bool) +

+
+                    (separator? c)
+                
+

+

checks whether the character c is a separator for the +PATH environment variable on this OS.

+ +

+
+
+ +

+ separators +

+
+
+ def +
+

+ (Array Char) +

+ + + +

+

is the possible separators we could use on this OS.

+ +

+
+
+ +

+ split +

+
+
+ defn +
+

+ (λ [&String] (Array String)) +

+
+                    (split p)
+                
+

+

splits the path p into its components.

+

As such, it is the inverse to join.

+ +

+
+
+ +

+ split-extension +

+
+
+ defn +
+

+ (λ [&String] (Maybe (Pair String String))) +

+
+                    (split-extension p)
+                
+

+

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"))
+
+ +

+
+
+ +

+ split-search-path +

+
+
+ defn +
+

+ (λ [&String] (Array String)) +

+
+                    (split-search-path p)
+                
+

+

splits a PATH environment variable p.

+ +

+
+
+ + diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..ed4f963 --- /dev/null +++ b/docs/style.css @@ -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; } diff --git a/gendocs.carp b/gendocs.carp new file mode 100644 index 0000000..f9730c4 --- /dev/null +++ b/gendocs.carp @@ -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) diff --git a/path.carp b/path.carp new file mode 100644 index 0000000..68f8be6 --- /dev/null +++ b/path.carp @@ -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)))) +) diff --git a/test/path.carp b/test/path.carp new file mode 100644 index 0000000..4ec532e --- /dev/null +++ b/test/path.carp @@ -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" + ) +) +())