std.string
case, coercion, path, plural, prose, and wrapping helpers
std.string is a higher-level standard library family in foundation-base. This page explains when to use it, how it fits internally, and where to find the API surface.
1 Motivation
Use this layer when application or tooling code needs the behavior described by the page title without reaching directly into implementation namespaces. The top-level namespace is the starting point; subnamespaces expose more focused building blocks.
2 How to use it
Require the top-level namespace for common workflows, then move to subnamespaces when you need a lower-level primitive. Existing tests under test/std/string and test/std/string_test.clj are the best executable examples for edge cases.
case conversions
(camel-case "hello-world")
=> "helloWorld"
(snake-case "hello-world")
=> "hello_world"
(spear-case "hello_world")
=> "hello-world"
wrapped operations preserve input type
((wrap split) :a-b #"-")
=> [:a :b]
((wrap subs) :hello-world 3 8)
=> :lo-wo
prose helpers
(prose/| "abc" "def")
=> "abc\ndef"
(prose/single-line "a\nb")
=> "a b"
3 Internal usage
This library family is used across source, tests, generated examples, and docs tooling. During detailed documentation passes, collect concrete usage with code.manage/find-usages and code.manage/locate-code, then keep only high-signal examples in the page narrative.
4 API
- +hump-pattern+
- +non-camel-pattern+
- +non-dot-pattern+
- +non-snake-pattern+
- +non-spear-pattern+
- blank?
- camel-case
- capital-case
- capital-sep-case
- capitalize
- caseless=
- decapitalize
- dot-case
- ends-with?
- escape
- escape-dollars
- escape-escapes
- escape-newlines
- escape-quotes
- filter-empty-lines
- from-string
- has-quotes?
- includes?
- indent
- indent-rest
- insert-at
- join
- join-lines
- joinl
- layout-lines
- lines
- lower-case
- lower-sep-case
- multi-line?
- pascal-case
- path-count
- path-join
- path-ns
- path-ns-array
- path-nth
- path-root
- path-separator
- path-split
- path-stem
- path-stem-array
- path-sub
- path-sub-array
- path-val
- phrase-case
- re-sub
- replace
- replace-all
- replace-at
- replace-by
- reverse
- separate-humps
- single-line
- single-line?
- snake-case
- spaces
- spear-case
- split
- split-lines
- starts-with?
- str:compare
- str:op
- strip-quotes
- to-string
- trim
- trim-left
- trim-newlines
- trim-right
- truncate
- typeless=
- upper-camel-case
- upper-case
- upper-sep-case
- whitespace?
- wrap
- write-line
- write-lines
- |
v 3.0
(defn blank?
([^CharSequence s]
(if s
(loop [index (int 0)]
(if (= (.length s) index)
true
(if (Character/isWhitespace (.charAt s index))
(recur (inc index))
false)))
true)))
link
(blank? nil) => true (blank? "") => true
v 3.0
(defn ^String camel-case
([^String value]
(let [s (re-sub value
+non-camel-pattern+
(fn [s] (common/upper-case (apply str (rest s)))))]
(str (.toLowerCase (subs s 0 1))
(subs s 1)))))
link
(camel-case "hello-world") => "helloWorld" ((wrap camel-case) 'hello_world) => 'helloWorld
v 3.0
(defn ^String capital-case
([^CharSequence s]
(let [s (.toString s)]
(if (< (count s) 2)
(.toUpperCase s)
(str (.toUpperCase (subs s 0 1))
(.toLowerCase (subs s 1)))))))
link
(capital-case "hello.World") => "Hello.world" ((wrap capital-case) 'hello.World) => 'Hello.world
capital-sep-case ^
[value]
converts a string-like object to captital case representation
v 3.0
(defn ^String capital-sep-case
([^String value]
(-> (separate-humps value)
(common/split #"[ |-|_]")
(->> (map common/capital-case))
(common/joinl " "))))
link
(capital-sep-case "hello world") => "Hello World" (str ((wrap capital-sep-case) :hello-world)) => ":Hello World"
v 4.0
(defn ^String capitalize
[^String s]
(str (.toUpperCase (subs s 0 1))
(subs s 1)))
link
(capitalize "hello") => "Hello"
v 3.0
(defn caseless=
([x y]
(= (lower-case x)
(lower-case y))))
link
(caseless= "heLLo" "HellO") => true ((wrap caseless=) 'heLLo :HellO) => true
v 4.0
(defn ^String decapitalize
[^String s]
(str (.toLowerCase (subs s 0 1))
(subs s 1)))
link
(decapitalize "HELLO") => "hELLO"
v 3.0
(defn ^String dot-case
([value]
(-> (separate-humps value)
(common/lower-case)
(common/replace +non-dot-pattern+ "."))))
link
(dot-case "hello-world") => "hello.world" ((wrap dot-case) 'helloWorld) => 'hello.world
v 3.0
(defn ends-with?
([^CharSequence s ^String substr]
(.endsWith (.toString s) substr)))
link
(ends-with? "hello" "lo") => true ((wrap ends-with?) 'hello 'lo) => true
v 3.0
(defn ^String escape
([^CharSequence s cmap]
(loop [index (int 0)
buffer (StringBuilder. (.length s))]
(if (= (.length s) index)
(.toString buffer)
(let [ch (.charAt s index)]
(if-let [replacement (cmap ch)]
(.append buffer replacement)
(.append buffer ch))
(recur (inc index) buffer))))))
link
(escape "1 < 2 as HTML, & s." {< "<", > ">", & "&"}) => "1 < 2 as HTML, & s."
v 3.0
(defn escape-dollars
([^String s]
(.replaceAll s "\$" "\\\$")))
link
(escape-dollars "$") => string?
v 3.0
(defn escape-escapes
([^String s]
(.replaceAll s "(\\)([A-Za-z])" "$1$1$2")))
link
(escape-escapes "\n") => "\\n"
v 3.0
(defn escape-newlines
([^String s]
(.replaceAll s "\n" "\\n")))
link
(escape-newlines "\n") => "\n"
v 3.0
(defn escape-quotes
([^String s]
(.replaceAll s "(\\)?"" "$1$1\\\"")))
link
(escape-quotes ""hello"") => "\"hello\""
v 3.0
(defn filter-empty-lines
[s]
(->> (common/split-lines s)
(remove (fn [line] (re-find #"^s*$" line)))
(common/join "n")))
link
(filter-empty-lines (common/join "n" ["a" " " " " "b"])) => "anb"
v 3.0
(defn from-string
([string type]
(from-string string type nil))
([string type opts]
(protocol.string/-from-string string type opts)))
link
(from-string "a" clojure.lang.Symbol) => 'a (from-string "std.string" clojure.lang.Namespace) => (find-ns 'std.string)
v 3.0
(defn has-quotes?
([^String s]
(and (.startsWith s """)
(.endsWith s """))))
link
(has-quotes? ""hello"") => true
v 3.0
(defn includes?
([^CharSequence s ^CharSequence substr]
(.contains (.toString s) substr)))
link
(includes? "hello" "ell") => true ((wrap includes?) 'hello 'ell) => true
v 4.0
(defn indent
([block n & [{:keys [custom] :or
{custom ""}}]]
(if (and (not (pos? n))
(empty? custom))
block
(->> (common/split-lines block)
(map (fn [line] (str (spaces n) custom line)))
(common/join "n")))))
link
(indent (write-lines ["a" "b" "c"]) 2) => "an bn c"
indent-rest ^
[block n & [{:keys [custom], :or {custom ""}}]]
indents the rest of the boiy
v 4.0
(defn indent-rest
([block n & [{:keys [custom] :or
{custom ""}}] ]
(if (and (not (pos? n))
(empty? custom))
block
(let [[head & rest] (common/split-lines block)]
(cond (empty? rest)
head
:else
(->> rest
(map (fn [line] (str (spaces n) custom line)))
(common/join "n")
(str head "n")))))))
link
(indent-rest (write-lines ["a" "b" "c"]) 2) => "an bn c"
v 4.0
(defn ^String insert-at
[^String s i new]
(str (subs s 0 i)
new
(subs s i)))
link
(insert-at "hello" 2 "XX") => "heXXllo"
v 3.0
(defn ^String join
([coll]
(apply str coll))
([seperator coll]
(joinl coll seperator)))
link
(join "." ["a" "b" "c"]) => "a.b.c" ;;(join "." '[a b c]) ;;=> 'a.b.c
v 4.0
(defn join-lines
[arr]
(common/join "n" arr))
link
(join-lines ["hello" "world"]) => "hellonworld"
v 3.0
(defn ^String joinl
([coll]
(apply str coll))
([coll separator]
(loop [sb (StringBuilder. (str (first coll)))
more (next coll)
sep (str separator)]
(if more
(recur (-> sb (.append sep) (.append (str (first more))))
(next more)
sep)
(str sb)))))
link
(joinl ["a" "b" "c"] ".") => "a.b.c" (joinl ["a" "b" "c"]) => "abc" ((wrap joinl) [:a :b :c] "-") => :a-b-c
v 4.0
(defn layout-lines
([tokens]
(layout-lines tokens 80))
([tokens max]
(->> (reduce (fn [[lines curr] tok]
(let [stok (str tok)]
(if (< (inc (+ (count curr)
(count stok)))
max)
[lines (str curr " " stok)]
[(conj lines curr) stok])))
[[] (str (first tokens))]
(rest tokens))
(apply conj)
(common/join "n"))))
link
(layout-lines ["hello" "world" "again" "a" "b"] 8) => "hellonworldnagain anb"
v 3.0
(defn lines
[s]
(cons `| (common/split-lines s)))
link
(lines "abcndef") => '(std.string.prose/| "abc" "def")
v 3.0
(defn ^String lower-case
([^CharSequence s]
(.. s toString toLowerCase)))
link
(lower-case "Hello.World") => "hello.world" ((wrap lower-case) 'Hello.World) => 'hello.world
v 3.0
(defn ^String lower-sep-case
([^String value]
(-> (separate-humps value)
(common/split #"[ |-|_]")
(->> (map common/lower-case))
(common/joinl " "))))
link
(lower-sep-case "helloWorld") => "hello world" ((wrap lower-sep-case) 'hello-world) => (symbol "hello world")
v 4.0
(defn multi-line?
([s]
(and s (boolean (re-find #"n" s)))))
link
(multi-line? "anb") => true (multi-line? "a") => false
v 3.0
(defn ^String pascal-case
([^String value]
(let [s (re-sub value +non-camel-pattern+
(fn [s] (common/upper-case (apply str (rest s)))))]
(str (.toUpperCase (subs s 0 1))
(subs s 1)))))
link
(pascal-case "helloWorld") => "HelloWorld" ((wrap pascal-case) :hello-world) => :HelloWorld
v 3.0
(defn path-count
([s]
(path-count s h/*sep*))
([s sep]
(count (path-split s sep))))
link
(path/path-count "a/b/c") => 3 ((wrap path/path-count) *ns*) => 3
v 3.0
(defn path-join
([arr] (path-join arr h/*sep*))
([arr sep]
(if (seq arr)
(-> (filter identity arr)
(common/joinl sep)))))
link
(path/path-join ["a" "b" "c"]) => "a/b/c" ((wrap path/path-join) '[:a :b :c] "-") => :a-b-c ((wrap path/path-join) '[a b c] '-) => 'a-b-c
v 3.0
(defn path-ns
([s]
(path-ns s h/*sep*))
([s sep]
(-> s
(path-ns-array sep)
(path-join sep))))
link
(path/path-ns "a/b/c/d") => "a/b/c" ((wrap path/path-ns) :a.b.c ".") => :a.b
v 3.0
(defn path-ns-array
([s]
(path-ns-array s h/*sep*))
([s sep]
(or (butlast (path-split s sep)) [])))
link
(path/path-ns-array "a/b/c/d") => ["a" "b" "c"] ((wrap path/path-ns-array) (keyword "a/b/c/d")) => [:a :b :c]
v 3.0
(defn path-nth
([s n]
(path-nth s n h/*sep*))
([s n sep]
(nth (path-split s sep) n)))
link
(path/path-nth "a/b/c/d" 2) => "c"
v 3.0
(defn path-root
([s]
(path-root s h/*sep*))
([s sep]
(first (path-ns-array s sep))))
link
(path/path-root "a/b/c/d") => "a" ((wrap path/path-root) 'a.b.c ".") => 'a
v 3.0
(definvoke path-separator
[:memoize {:arglists '([type])
:function protocol.string/-path-separator}])
link
(path-separator clojure.lang.Namespace) => "." (path-separator clojure.lang.Keyword) => "/"
v 3.0
(defn path-split
([s] (path-split s h/*sep*))
([s sep]
(common/split s (h/re-create sep))))
link
(path/path-split "a/b/c/d") => '["a" "b" "c" "d"] (path/path-split "a.b.c.d" ".") => ["a" "b" "c" "d"] ((wrap path/path-split) :hello/world) => [:hello :world] ((wrap path/path-split) :hello.world ".") => [:hello :world]
v 3.0
(defn path-stem
([s]
(path-stem s h/*sep*))
([s sep]
(-> s
(path-stem-array sep)
(path-join sep))))
link
(path/path-stem "a/b/c/d") => "b/c/d" ((wrap path/path-stem) 'a.b.c.d ".") => 'b.c.d
v 3.0
(defn path-stem-array
([s]
(path-stem-array s h/*sep*))
([s sep]
(rest (path-split s sep))))
link
(path/path-stem-array "a/b/c/d") => ["b" "c" "d"] ((wrap path/path-stem-array) 'a.b.c.d ".") => '[b c d]
path-sub ^
[s start num] [s start num sep]
returns a subsection of the path within the string
v 3.0
(defn path-sub
([s start num]
(path-sub s start num h/*sep*))
([s start num sep]
(-> (path-sub-array s start num sep)
(path-join sep))))
link
(path/path-sub "a/b/c/d" 1 2) => "b/c" ((wrap path/path-sub) (symbol "a/b/c/d") 1 2) => 'b/c
path-sub-array ^
[s start num] [s start num sep]
returns a sub array of the path within the string
v 3.0
(defn path-sub-array
([s start num]
(path-sub-array s start num h/*sep*))
([s start num sep]
(->> (path-split s sep)
(drop start)
(take num))))
link
(path/path-sub-array "a/b/c/d" 1 2) => ["b" "c"] ((wrap path/path-sub-array) (symbol "a/b/c/d") 1 2) => '[b c]
v 3.0
(defn path-val
([s]
(path-val s h/*sep*))
([s sep]
(last (path-split s sep))))
link
(path/path-val "a/b/c/d") => "d" ((wrap path/path-val) 'a.b.c.d ".") => 'd
v 3.0
(defn ^String phrase-case
([^String value]
(let [s (lower-sep-case value)]
(str (.toUpperCase (subs s 0 1))
(subs s 1)))))
link
(phrase-case "hello-world") => "Hello world"
v 3.0
(defn- ^String re-sub
([^String value pattern sub-func]
(loop [matcher (re-matcher pattern value)
result []
last-end 0]
(if (.find matcher)
(recur matcher
(conj result
(.substring value last-end (.start matcher))
(sub-func (re-groups matcher)))
(.end matcher))
(apply str (conj result (.substring value last-end)))))))
link
(#'case/re-sub "aTa" @#'case/+hump-pattern+ (fn [_] "$")) => "$a"
v 3.0
(defn ^String replace
([^CharSequence s match replacement]
(let [s (.toString s)]
(cond
(instance? Character match) (.replace s ^Character match ^Character replacement)
(instance? CharSequence match) (.replace s ^CharSequence match ^CharSequence replacement)
(instance? Pattern match) (if (instance? CharSequence replacement)
(.replaceAll (re-matcher ^Pattern match s)
(.toString ^CharSequence replacement))
(replace-by s match replacement))
:else (throw (IllegalArgumentException. (str "Invalid match arg: " match)))))))
link
(replace "hello" "el" "AL") => "hALlo" ((wrap replace) :hello "el" "AL") => :hALlo
v 4.0
(defn ^String replace-all
[^String s match re]
(.replaceAll s match re))
link
(replace-all "hello" "l" "o") => "heooo"
v 4.0
(defn ^String replace-at
[^String s i new]
(str (subs s 0 i)
new
(subs s (+ i 1))))
link
(replace-at "hello" 4 "a") => "hella" (replace-at "hello" 1 "a") => "hallo"
replace-by ^
NONE
(defn- replace-by
[^CharSequence s re f]
(let [m (re-matcher re s)]
(if (.find m)
(let [buffer (StringBuffer. (.length s))]
(loop [found true]
(if found
(do (.appendReplacement m buffer (Matcher/quoteReplacement (f (re-groups m))))
(recur (.find m)))
(do (.appendTail m buffer)
(.toString buffer)))))
s)))
link
v 3.0
(defn ^String reverse
([^CharSequence s]
(.toString (.reverse (StringBuilder. s)))))
link
(reverse "hello") => "olleh" ((wrap reverse) :hello) => :olleh
v 3.0
(defn- ^String separate-humps
([^String value]
(re-sub value +hump-pattern+ #(common/joinl (seq %) " "))))
link
(#'case/separate-humps "aTaTa") => "a Ta Ta"
v 3.0
(defn single-line
[s]
(->> (common/split-lines s)
(common/join " ")))
link
(single-line "anbnc") => "a b c"
v 4.0
(defn single-line?
([s]
(not (multi-line? s))))
link
(single-line? "anb") => false (single-line? "a") => true
v 3.0
(defn ^String snake-case
([value]
(-> (separate-humps value)
(common/lower-case)
(common/replace +non-snake-pattern+ "_"))))
link
(snake-case "hello-world") => "hello_world" ((wrap snake-case) 'helloWorld) => 'hello_world
v 3.0
(defn ^String spear-case
([value]
(-> (separate-humps value)
(common/lower-case)
(common/replace +non-spear-pattern+ "-"))))
link
(spear-case "hello_world") => "hello-world" ((wrap spear-case) 'helloWorld) => 'hello-world
v 3.0
(defn ^String split
([^CharSequence s ^Pattern re]
(vec (.split re s -1))))
link
(split "a b c" #" ") => ["a" "b" "c"] ((wrap split) :a.b.c (re-pattern "\.")) => [:a :b :c]
v 3.0
(defn split-lines
([^String s]
(if (not-empty s)
(vec (.split s "[\r\n]", -1))
[])))
link
(split-lines "anbnc") => ["a" "b" "c"]
v 3.0
(defn starts-with?
([^CharSequence s ^String substr]
(.startsWith (.toString s) substr)))
link
(starts-with? "hello" "hel") => true ((wrap starts-with?) 'hello 'hel) => true
v 3.0
(defn str:compare
([f]
(fn [x y & args]
(binding [h/*sep* (path-separator (type x))]
(apply f (to-string x) (to-string y) args)))))
link
((str:compare =) :hello 'hello) => true
str:op ^
[f] [f return]
wraps a string function such that it can take any string-like input
v 3.0
(defn str:op
([f]
(str:op f false))
([f return]
(fn [input & args]
(let [[isample iarr?] (if (sequential? input)
[(first input) true]
[input false])
class (type isample)]
(binding [h/*sep* (path-separator class)]
(let [interim (cond (= class String)
(apply f input args)
iarr?
(apply f (map to-string input) args)
:else
(apply f (to-string input) args))
oarr? (sequential? interim)
output (cond return
interim
(= class String)
interim
oarr?
(mapv #(from-string % class) interim)
:else
(from-string interim class))]
output))))))
link
((str:op str false) :hello 'hello) => :hellohello ((str:op str true) :hello 'hello) => "hellohello"
v 3.0
(defn strip-quotes
([s]
(if (has-quotes? s)
(subs s 1 (dec (count s)))
s)))
link
(strip-quotes ""hello"") => "hello"
v 3.0
(defn ^String to-string
([string]
(protocol.string/-to-string string)))
link
(to-string :hello/world) => "hello/world" (to-string *ns*) => "std.string.coerce-test"
v 3.0
(defn ^String trim
([^CharSequence s]
(let [len (.length s)]
(loop [rindex len]
(if (zero? rindex)
""
(if (Character/isWhitespace (.charAt s (dec rindex)))
(recur (dec rindex))
;; there is at least one non-whitespace char in the string,
;; so no need to check for lindex reaching len.
(loop [lindex 0]
(if (Character/isWhitespace (.charAt s lindex))
(recur (inc lindex))
(.. s (subSequence lindex rindex) toString)))))))))
link
(trim " hello ") => "hello"
v 3.0
(defn ^String trim-left
([^CharSequence s]
(let [len (.length s)]
(loop [index 0]
(if (= len index)
""
(if (Character/isWhitespace (.charAt s index))
(recur (unchecked-inc index))
(.. s (subSequence index len) toString)))))))
link
(trim-left " hello ") => "hello "
v 3.0
(defn ^String trim-newlines
([^CharSequence s]
(loop [index (.length s)]
(if (zero? index)
""
(let [ch (.charAt s (dec index))]
(if (or (= ch newline) (= ch return))
(recur (dec index))
(.. s (subSequence 0 index) toString)))))))
link
(trim-newlines "nn hello nn") => "nn hello "
v 3.0
(defn ^String trim-right
([^CharSequence s]
(loop [index (.length s)]
(if (zero? index)
""
(if (Character/isWhitespace (.charAt s (unchecked-dec index)))
(recur (unchecked-dec index))
(.. s (subSequence 0 index) toString))))))
link
(trim-right " hello ") => "hello"
v 3.0
(defn ^String truncate
([s limit]
(let [len (count s)]
(if (> len limit)
(subs s 0 limit)
s))))
link
(truncate "hello there" 5) => "hello"
v 3.0
(defn ^String typeless=
([x y]
(= (lower-sep-case x)
(lower-sep-case y))))
link
(typeless= "helloWorld" "hello_world") => true ((wrap typeless=) :a-b-c "a b c") => true ((wrap typeless=) 'getMethod :get-method) => true
v 4.0
(defn ^String upper-camel-case
([^String value]
(let [s (re-sub value
+non-camel-pattern+
(fn [s] (common/upper-case (apply str (rest s)))))]
(str (.toUpperCase (subs s 0 1))
(subs s 1)))))
link
(upper-camel-case "hello-world") => "HelloWorld"
v 3.0
(defn ^String upper-case
([^CharSequence s]
(.. s toString toUpperCase)))
link
(upper-case "hello-world") => "HELLO-WORLD" ((wrap upper-case) :hello-world) => :HELLO-WORLD
v 3.0
(defn ^String upper-sep-case
([^String value]
(-> (separate-humps value)
(common/split #"[ |-|_]")
(->> (map common/upper-case))
(common/joinl " "))))
link
(upper-sep-case "hello world") => "HELLO WORLD" (str ((wrap upper-sep-case) 'hello-world)) => "HELLO WORLD"
v 3.0
(defn whitespace?
([s]
(boolean (or (= "" s) (re-find #"^[st]+$" s)))))
link
(whitespace? " ") => true
v 3.0
(definvoke wrap
[:memoize]
([f]
(let [wrapper (get *lookup* f)
wrapper (if wrapper
(wrap-fn wrapper)
(wrap-fn f))]
(wrapper f))))
link
((wrap #(str '+ % '+)) 'hello) => '+hello+
v 4.0
(defn write-line
([v]
(if (vector? v)
(let [tag (first (keys (meta v)))
body (common/join " " (map write-line v))
body (if (#{"" :quote} tag)
(str "'" body "'")
body)]
body)
(h/strn v))))
link
(write-line [:a :b ["hello" "world"]]) => "a b hello world"
- +hump-pattern+
- +non-camel-pattern+
- +non-dot-pattern+
- +non-snake-pattern+
- +non-spear-pattern+
- camel-case
- capital-sep-case
- dot-case
- lower-sep-case
- pascal-case
- phrase-case
- re-sub
- separate-humps
- snake-case
- spear-case
- typeless=
- upper-camel-case
- upper-sep-case
v 3.0
(defn ^String camel-case
([^String value]
(let [s (re-sub value
+non-camel-pattern+
(fn [s] (common/upper-case (apply str (rest s)))))]
(str (.toLowerCase (subs s 0 1))
(subs s 1)))))
link
(camel-case "hello-world") => "helloWorld" ((wrap camel-case) 'hello_world) => 'helloWorld
capital-sep-case ^
[value]
converts a string-like object to captital case representation
v 3.0
(defn ^String capital-sep-case
([^String value]
(-> (separate-humps value)
(common/split #"[ |-|_]")
(->> (map common/capital-case))
(common/joinl " "))))
link
(capital-sep-case "hello world") => "Hello World" (str ((wrap capital-sep-case) :hello-world)) => ":Hello World"
v 3.0
(defn ^String dot-case
([value]
(-> (separate-humps value)
(common/lower-case)
(common/replace +non-dot-pattern+ "."))))
link
(dot-case "hello-world") => "hello.world" ((wrap dot-case) 'helloWorld) => 'hello.world
v 3.0
(defn ^String lower-sep-case
([^String value]
(-> (separate-humps value)
(common/split #"[ |-|_]")
(->> (map common/lower-case))
(common/joinl " "))))
link
(lower-sep-case "helloWorld") => "hello world" ((wrap lower-sep-case) 'hello-world) => (symbol "hello world")
v 3.0
(defn ^String pascal-case
([^String value]
(let [s (re-sub value +non-camel-pattern+
(fn [s] (common/upper-case (apply str (rest s)))))]
(str (.toUpperCase (subs s 0 1))
(subs s 1)))))
link
(pascal-case "helloWorld") => "HelloWorld" ((wrap pascal-case) :hello-world) => :HelloWorld
v 3.0
(defn ^String phrase-case
([^String value]
(let [s (lower-sep-case value)]
(str (.toUpperCase (subs s 0 1))
(subs s 1)))))
link
(phrase-case "hello-world") => "Hello world"
v 3.0
(defn- ^String re-sub
([^String value pattern sub-func]
(loop [matcher (re-matcher pattern value)
result []
last-end 0]
(if (.find matcher)
(recur matcher
(conj result
(.substring value last-end (.start matcher))
(sub-func (re-groups matcher)))
(.end matcher))
(apply str (conj result (.substring value last-end)))))))
link
(#'case/re-sub "aTa" @#'case/+hump-pattern+ (fn [_] "$")) => "$a"
v 3.0
(defn- ^String separate-humps
([^String value]
(re-sub value +hump-pattern+ #(common/joinl (seq %) " "))))
link
(#'case/separate-humps "aTaTa") => "a Ta Ta"
v 3.0
(defn ^String snake-case
([value]
(-> (separate-humps value)
(common/lower-case)
(common/replace +non-snake-pattern+ "_"))))
link
(snake-case "hello-world") => "hello_world" ((wrap snake-case) 'helloWorld) => 'hello_world
v 3.0
(defn ^String spear-case
([value]
(-> (separate-humps value)
(common/lower-case)
(common/replace +non-spear-pattern+ "-"))))
link
(spear-case "hello_world") => "hello-world" ((wrap spear-case) 'helloWorld) => 'hello-world
v 3.0
(defn ^String typeless=
([x y]
(= (lower-sep-case x)
(lower-sep-case y))))
link
(typeless= "helloWorld" "hello_world") => true ((wrap typeless=) :a-b-c "a b c") => true ((wrap typeless=) 'getMethod :get-method) => true
v 4.0
(defn ^String upper-camel-case
([^String value]
(let [s (re-sub value
+non-camel-pattern+
(fn [s] (common/upper-case (apply str (rest s)))))]
(str (.toUpperCase (subs s 0 1))
(subs s 1)))))
link
(upper-camel-case "hello-world") => "HelloWorld"
v 3.0
(defn from-string
([string type]
(from-string string type nil))
([string type opts]
(protocol.string/-from-string string type opts)))
link
(from-string "a" clojure.lang.Symbol) => 'a (from-string "std.string" clojure.lang.Namespace) => (find-ns 'std.string)
v 3.0
(definvoke path-separator
[:memoize {:arglists '([type])
:function protocol.string/-path-separator}])
link
(path-separator clojure.lang.Namespace) => "." (path-separator clojure.lang.Keyword) => "/"
v 3.0
(defn str:compare
([f]
(fn [x y & args]
(binding [h/*sep* (path-separator (type x))]
(apply f (to-string x) (to-string y) args)))))
link
((str:compare =) :hello 'hello) => true
str:op ^
[f] [f return]
wraps a string function such that it can take any string-like input
v 3.0
(defn str:op
([f]
(str:op f false))
([f return]
(fn [input & args]
(let [[isample iarr?] (if (sequential? input)
[(first input) true]
[input false])
class (type isample)]
(binding [h/*sep* (path-separator class)]
(let [interim (cond (= class String)
(apply f input args)
iarr?
(apply f (map to-string input) args)
:else
(apply f (to-string input) args))
oarr? (sequential? interim)
output (cond return
interim
(= class String)
interim
oarr?
(mapv #(from-string % class) interim)
:else
(from-string interim class))]
output))))))
link
((str:op str false) :hello 'hello) => :hellohello ((str:op str true) :hello 'hello) => "hellohello"
- path-count
- path-join
- path-ns
- path-ns-array
- path-nth
- path-root
- path-split
- path-stem
- path-stem-array
- path-sub
- path-sub-array
- path-val
v 3.0
(defn path-count
([s]
(path-count s h/*sep*))
([s sep]
(count (path-split s sep))))
link
(path/path-count "a/b/c") => 3 ((wrap path/path-count) *ns*) => 3
v 3.0
(defn path-join
([arr] (path-join arr h/*sep*))
([arr sep]
(if (seq arr)
(-> (filter identity arr)
(common/joinl sep)))))
link
(path/path-join ["a" "b" "c"]) => "a/b/c" ((wrap path/path-join) '[:a :b :c] "-") => :a-b-c ((wrap path/path-join) '[a b c] '-) => 'a-b-c
v 3.0
(defn path-ns
([s]
(path-ns s h/*sep*))
([s sep]
(-> s
(path-ns-array sep)
(path-join sep))))
link
(path/path-ns "a/b/c/d") => "a/b/c" ((wrap path/path-ns) :a.b.c ".") => :a.b
v 3.0
(defn path-ns-array
([s]
(path-ns-array s h/*sep*))
([s sep]
(or (butlast (path-split s sep)) [])))
link
(path/path-ns-array "a/b/c/d") => ["a" "b" "c"] ((wrap path/path-ns-array) (keyword "a/b/c/d")) => [:a :b :c]
v 3.0
(defn path-nth
([s n]
(path-nth s n h/*sep*))
([s n sep]
(nth (path-split s sep) n)))
link
(path/path-nth "a/b/c/d" 2) => "c"
v 3.0
(defn path-root
([s]
(path-root s h/*sep*))
([s sep]
(first (path-ns-array s sep))))
link
(path/path-root "a/b/c/d") => "a" ((wrap path/path-root) 'a.b.c ".") => 'a
v 3.0
(defn path-split
([s] (path-split s h/*sep*))
([s sep]
(common/split s (h/re-create sep))))
link
(path/path-split "a/b/c/d") => '["a" "b" "c" "d"] (path/path-split "a.b.c.d" ".") => ["a" "b" "c" "d"] ((wrap path/path-split) :hello/world) => [:hello :world] ((wrap path/path-split) :hello.world ".") => [:hello :world]
v 3.0
(defn path-stem
([s]
(path-stem s h/*sep*))
([s sep]
(-> s
(path-stem-array sep)
(path-join sep))))
link
(path/path-stem "a/b/c/d") => "b/c/d" ((wrap path/path-stem) 'a.b.c.d ".") => 'b.c.d
v 3.0
(defn path-stem-array
([s]
(path-stem-array s h/*sep*))
([s sep]
(rest (path-split s sep))))
link
(path/path-stem-array "a/b/c/d") => ["b" "c" "d"] ((wrap path/path-stem-array) 'a.b.c.d ".") => '[b c d]
path-sub ^
[s start num] [s start num sep]
returns a subsection of the path within the string
v 3.0
(defn path-sub
([s start num]
(path-sub s start num h/*sep*))
([s start num sep]
(-> (path-sub-array s start num sep)
(path-join sep))))
link
(path/path-sub "a/b/c/d" 1 2) => "b/c" ((wrap path/path-sub) (symbol "a/b/c/d") 1 2) => 'b/c
path-sub-array ^
[s start num] [s start num sep]
returns a sub array of the path within the string
v 3.0
(defn path-sub-array
([s start num]
(path-sub-array s start num h/*sep*))
([s start num sep]
(->> (path-split s sep)
(drop start)
(take num))))
link
(path/path-sub-array "a/b/c/d" 1 2) => ["b" "c"] ((wrap path/path-sub-array) (symbol "a/b/c/d") 1 2) => '[b c]
- escape-dollars
- escape-escapes
- escape-newlines
- escape-quotes
- filter-empty-lines
- has-quotes?
- indent
- indent-rest
- join-lines
- layout-lines
- lines
- multi-line?
- single-line
- single-line?
- spaces
- strip-quotes
- whitespace?
- write-line
- write-lines
- |
v 3.0
(defn escape-dollars
([^String s]
(.replaceAll s "\$" "\\\$")))
link
(escape-dollars "$") => string?
v 3.0
(defn escape-escapes
([^String s]
(.replaceAll s "(\\)([A-Za-z])" "$1$1$2")))
link
(escape-escapes "\n") => "\\n"
v 3.0
(defn escape-newlines
([^String s]
(.replaceAll s "\n" "\\n")))
link
(escape-newlines "\n") => "\n"
v 3.0
(defn escape-quotes
([^String s]
(.replaceAll s "(\\)?"" "$1$1\\\"")))
link
(escape-quotes ""hello"") => "\"hello\""
v 3.0
(defn filter-empty-lines
[s]
(->> (common/split-lines s)
(remove (fn [line] (re-find #"^s*$" line)))
(common/join "n")))
link
(filter-empty-lines (common/join "n" ["a" " " " " "b"])) => "anb"
v 3.0
(defn has-quotes?
([^String s]
(and (.startsWith s """)
(.endsWith s """))))
link
(has-quotes? ""hello"") => true
v 4.0
(defn indent
([block n & [{:keys [custom] :or
{custom ""}}]]
(if (and (not (pos? n))
(empty? custom))
block
(->> (common/split-lines block)
(map (fn [line] (str (spaces n) custom line)))
(common/join "n")))))
link
(indent (write-lines ["a" "b" "c"]) 2) => "an bn c"
indent-rest ^
[block n & [{:keys [custom], :or {custom ""}}]]
indents the rest of the boiy
v 4.0
(defn indent-rest
([block n & [{:keys [custom] :or
{custom ""}}] ]
(if (and (not (pos? n))
(empty? custom))
block
(let [[head & rest] (common/split-lines block)]
(cond (empty? rest)
head
:else
(->> rest
(map (fn [line] (str (spaces n) custom line)))
(common/join "n")
(str head "n")))))))
link
(indent-rest (write-lines ["a" "b" "c"]) 2) => "an bn c"
v 4.0
(defn join-lines
[arr]
(common/join "n" arr))
link
(join-lines ["hello" "world"]) => "hellonworld"
v 4.0
(defn layout-lines
([tokens]
(layout-lines tokens 80))
([tokens max]
(->> (reduce (fn [[lines curr] tok]
(let [stok (str tok)]
(if (< (inc (+ (count curr)
(count stok)))
max)
[lines (str curr " " stok)]
[(conj lines curr) stok])))
[[] (str (first tokens))]
(rest tokens))
(apply conj)
(common/join "n"))))
link
(layout-lines ["hello" "world" "again" "a" "b"] 8) => "hellonworldnagain anb"
v 3.0
(defn lines
[s]
(cons `| (common/split-lines s)))
link
(lines "abcndef") => '(std.string.prose/| "abc" "def")
v 4.0
(defn multi-line?
([s]
(and s (boolean (re-find #"n" s)))))
link
(multi-line? "anb") => true (multi-line? "a") => false
v 3.0
(defn single-line
[s]
(->> (common/split-lines s)
(common/join " ")))
link
(single-line "anbnc") => "a b c"
v 4.0
(defn single-line?
([s]
(not (multi-line? s))))
link
(single-line? "anb") => false (single-line? "a") => true
v 3.0
(defn strip-quotes
([s]
(if (has-quotes? s)
(subs s 1 (dec (count s)))
s)))
link
(strip-quotes ""hello"") => "hello"
v 3.0
(defn whitespace?
([s]
(boolean (or (= "" s) (re-find #"^[st]+$" s)))))
link
(whitespace? " ") => true
v 4.0
(defn write-line
([v]
(if (vector? v)
(let [tag (first (keys (meta v)))
body (common/join " " (map write-line v))
body (if (#{"" :quote} tag)
(str "'" body "'")
body)]
body)
(h/strn v))))
link
(write-line [:a :b ["hello" "world"]]) => "a b hello world"
*lookup* ^
NONE
(def ^:dynamic *lookup*
(reduce (fn [out [k arr]]
(into out (map vector arr (repeat k))))
{}
+defaults+))
link
v 3.0
(defn join
([arr]
(join arr h/*sep*))
([sep arr]
(coerce/from-string (common/join sep (map coerce/to-string arr))
(type (first arr)))))
link
(join "." [:a :b :c]) => :a.b.c
5 std.string: A Comprehensive Summary
The std.string module in foundation-base provides a rich set of utilities for manipulating, transforming, and analyzing strings in Clojure. It extends core string functionalities, offers various casing conversions, path manipulation, pluralization/singularization, and prose formatting. A key feature is its wrap mechanism, which allows these string operations to work seamlessly across different "string-like" types (e.g., keywords, symbols, namespaces, byte arrays) by coercing them to strings, applying the operation, and then coercing them back to their original type.
The module is organized into several sub-namespaces:
5.1 std.string.case
This namespace focuses on converting strings between various common casing conventions.
camel-case [value]: Converts tocamelCase(e.g., "hello-world" -> "helloWorld").nupper-camel-case [value]: Converts toUpperCamelCase(e.g., "hello-world" -> "HelloWorld").ncapital-sep-case [value]: Converts toCapital Separated Case(e.g., "hello world" -> "Hello World").nlower-sep-case [value]: Converts tolower separated case(e.g., "helloWorld" -> "hello world").npascal-case [value]: Converts toPascalCase(same asupper-camel-case).nphrase-case [value]: Converts toPhrase case(e.g., "hello-world" -> "Hello world").ndot-case [value]: Converts todot.case(e.g., "hello-world" -> "hello.world").nsnake-case [value]: Converts tosnake_case(e.g., "hello-world" -> "helloworld").nspear-case [value]: Converts tospear-case(e.g., "helloworld" -> "hello-world").nupper-sep-case [value]: Converts toUPPER SEPARATED CASE(e.g., "hello world" -> "HELLO WORLD").ntypeless= [x y]: Compares two string-like objects for equality, ignoring casing and separators (e.g., "helloWorld" and "hello_world" aretrue).
5.2 std.string.coerce
This namespace provides the core mechanisms for coercing various types to and from strings, and for defining how string operations should behave for different types.
from-string [string type & [opts]]: Converts a string to an object of a specifiedtype(e.g.,clojure.lang.Symbol,clojure.lang.Namespace).nto-string [string]: Converts an object to its string representation.npath-separator [type]: A multimethod that returns the default path separator for a given type (e.g.,.forNamespace,/forKeyword).nstr:op [f & [return]]: A higher-order function that wraps a string functionf. It automatically coerces input arguments to strings, appliesf, and then coerces the result back to the original input type (orStringifreturnistrue).nstr:compare [f]: A higher-order function that wraps a comparison functionf. It coerces two string-like inputs to strings before applyingf.nprotocol.string/IStringextensions: Extendsnil,Object,String,byte[],char[],Class,clojure.lang.Keyword,clojure.lang.Symbol,clojure.lang.Namespaceto implementIStringforto-stringfunctionality.nprotocol.string/-from-stringextensions: ExtendsString,byte[],char[],Class,clojure.lang.Keyword,clojure.lang.Symbol,clojure.lang.Namespaceforfrom-stringfunctionality.n*protocol.string/-path-separatorextensions: ExtendsClassandclojure.lang.Namespaceforpath-separatorfunctionality.
5.3 std.string.common
This namespace offers a collection of general-purpose string utilities, including checks, splits, joins, case conversions, and replacements.
blank? [s]: Checks if a string isnilor contains only whitespace.nsplit [s re]: Splits a string by a regular expression into a vector.nsplit-lines [s]: Splits a string into a vector of lines.njoinl [coll & [separator]]: Joins a collection of strings with an optional separator.njoin [separator coll]: Similar tojoinl, but with arguments reordered for threading.nupper-case [s]: Converts a string to uppercase.nlower-case [s]: Converts a string to lowercase.ncapital-case [s]: Capitalizes the first letter of a string and lowercases the rest.nreverse [s]: Reverses a string.nstarts-with? [s substr]: Checks if a string starts with a substring.nends-with? [s substr]: Checks if a string ends with a substring.nincludes? [s substr]: Checks if a string contains a substring.ntrim [s]: Trims leading and trailing whitespace.ntrim-left [s]: Trims leading whitespace.ntrim-right [s]: Trims trailing whitespace.ntrim-newlines [s]: Trims trailing newlines.nescape [s cmap]: Escapes characters in a string using a character map.nreplace [s match replacement]: Replaces occurrences ofmatchwithreplacement.matchcan be a character, string, orPattern.ncaseless= [x y]: Compares two strings for equality, ignoring case.ntruncate [s limit]: Truncates a string to a specifiedlimit.ncapitalize [s]: Capitalizes the first letter of a string.ndecapitalize [s]: Lowercases the first letter of a string.nreplace-all [s match re]: Shortcut forString.replaceAll.
5.4 std.string.path
This namespace provides functions for manipulating string representations of paths, treating them as hierarchical structures.
path-join [arr & [sep]]: Joins a sequence of path elements into a path string using a separator (defaults toh/*sep*).npath-split [s & [sep]]: Splits a path string into a sequence of elements using a separator (defaults toh/*sep*).npath-ns-array [s & [sep]]: Returns all but the last element of a path as a vector (the "namespace" part).npath-ns [s & [sep]]: Returns the "namespace" part of a path as a joined string.npath-root [s & [sep]]: Returns the first element of a path (the "root").npath-stem-array [s & [sep]]: Returns all but the first element of a path as a vector (the "stem" part).npath-stem [s & [sep]]: Returns the "stem" part of a path as a joined string.npath-val [s & [sep]]: Returns the last element of a path (the "value").npath-nth [s n & [sep]]: Returns then-th element of a path.npath-sub-array [s start num & [sep]]: Returns a sub-array of path elements.npath-sub [s start num & [sep]]: Returns a sub-section of a path as a joined string.n*path-count [s & [sep]]: Counts the number of elements in a path.
5.5 std.string.plural
This namespace provides functionalities for pluralizing and singularizing English words, handling irregular forms and uncountable nouns.
*uncountable*,*singular-irregular*,*plural-irregular*,*plural-rules*,*singular-rules*: Dynamic vars (atoms) holding the rules and exceptions for pluralization/singularization.n+base-uncountable+,+base-irregular+,+base-plural+,+base-singular+: Initial data for the rules.nuncountable? [word]: Checks if a word is in the list of uncountable nouns.nirregular? [word]: Checks if a word is an irregular plural/singular form.nresolve-rules [rules word]: Applies a set of rules to a word to find a matching pattern and replacement.nsingular [s]: Converts a word to its singular form.nplural [s]: Converts a word to its plural form.n**initalized*: A var that ensures the base rules are loaded into the dynamic atoms upon namespace loading.
5.6 std.string.prose
This namespace offers utilities for formatting and manipulating text as prose, including handling quotes, whitespace, line breaks, and indentation.
has-quotes? [s]: Checks if a string is enclosed in double quotes.nstrip-quotes [s]: Removes leading and trailing double quotes from a string.nwhitespace? [s]: Checks if a string consists entirely of whitespace.nescape-dollars [s]: Escapes dollar signs in a string for regex purposes.nescape-newlines [s]: Escapes newline characters (\n) to\\nfor printable representation.nescape-escapes [s]: Escapes backslashes that precede letters.nescape-quotes [s]: Escapes double quotes in a string.nfilter-empty-lines [s]: Removes empty or whitespace-only lines from a multi-line string.nsingle-line [s]: Replaces newlines with spaces, converting a multi-line string to a single line.njoin-lines [arr]: Joins non-empty elements of an array with newlines.nspaces [n]: Creates a string withnspaces.nwrite-line [v]: Writes a single line based on a data structure, handling nested structures.nwrite-lines [lines]: Writes a block of strings, joining them with newlines.nindent [block n & [opts]]: Indents a block of text bynspaces, optionally with a custom prefix.nindent-rest [block n & [opts]]: Indents all lines of a block except the first.nmulti-line? [s]: Checks if a string contains newline characters.nsingle-line? [s]: Checks if a string does not contain newline characters.n*layout-lines [tokens & [max]]: Lays out a sequence of tokens into lines, respecting a maximum line length.
5.7 std.string.wrap
This namespace provides the core wrap functionality, which is a powerful mechanism for extending string operations to work with various "string-like" types.
wrap-fn [f]: A multimethod that determines the appropriate wrapper function for a given string operationf.n:defaultmethod usescoerce/str:op(coerces to string, appliesf, coerces back).n:returnmethod usescoerce/str:opwithreturnset totrue(always returns a string).n:comparemethod usescoerce/str:compare(coerces both inputs to string, appliesf).n+defaults+: A map defining which string functions should use whichwrap-fnmethod.n*lookup*: A dynamic var (atom) that maps string functions to their corresponding wrapper types (e.g.,:compare,:return,:default).njoin [arr & [sep]]: An extended version ofcommon/jointhat works with string-like types.n*wrap [f]: The main macro that takes a string functionfand returns a new function that can operate on string-like types. It uses*lookup*andwrap-fnto determine the correct coercion and wrapping logic.
Overall Importance:
The std.string module is a fundamental utility within the foundation-base project. Its comprehensive set of string manipulation tools, combined with the powerful wrap mechanism, enables:
- Flexible String Handling: Operations can be applied uniformly to various string-like data types, reducing boilerplate and improving code readability.n Data Normalization: Standardized casing and path manipulation functions are crucial for consistent data processing and interoperability with external systems.n Code Generation and Transformation: The ability to manipulate strings and symbols effectively is vital for meta-programming tasks, such as generating code in different target languages.n Improved Readability and Formatting: Prose-related functions help in presenting information clearly and consistently.n Extensibility: The protocol-based coercion and wrapping mechanisms allow for easy extension to new "string-like" types as needed.
By providing these versatile and extensible string utilities, std.string significantly contributes to the foundation-base project's ability to handle diverse data formats and support its multi-language development ecosystem.