The clojure.string case fns and searches now take any Object s through its toString like the reference's ^CharSequence signatures ((upper-case :kw) is ":KW", (capitalize 1) is "1"); nil throws, and a nil substr in starts-with?/ends-with? throws. some-fn re-ported with the reference arities: (some-fn) is an arity error and a no-match result is the last predicate's own falsy value (false, not nil). ifn? covers multimethods, promises (which are now invocable — calling one delivers, via a cold-path invoke-arm registry that costs the hot dispatch nothing), and deftypes implementing IFn's invoke. One structural find on the way: defmulti/defmethod deferred inside a fn body (the deftest pattern) interned/resolved in whatever namespace was current when they RAN, not the one they were written in — the macros now bake their expansion ns and the setups honor it. Also: Boolean/Integer/Double wrapper ctors, primitive TYPE statics (Integer/TYPE etc.), .reduce on collections (IReduce), and Long/TYPE. cts baseline 5857 -> 5904 pass, 58 -> 28 errors, 57 baselined namespaces — the string cluster, some-fn, ifn-qmark, boolean-qmark, and reduce namespaces are all fully clean. 7 JVM-certified corpus rows; spec entry.
154 lines
3.9 KiB
Clojure
154 lines
3.9 KiB
Clojure
; Jolt Standard Library: clojure.string
|
|
; String manipulation functions using Jolt core string interop.
|
|
|
|
(defn blank?
|
|
[s]
|
|
(if (nil? s) true
|
|
(= 0 (count (str-trim s)))))
|
|
|
|
;; The case fns and the searches take any Object s through its toString, like
|
|
;; the reference ((upper-case :kw) is ":KW", (capitalize 1) is "1"); nil throws
|
|
;; like calling a method on null.
|
|
(defn- to-str [s]
|
|
(if (nil? s)
|
|
(throw (new NullPointerException "s"))
|
|
(.toString s)))
|
|
(defn capitalize
|
|
[s]
|
|
(let [s (to-str s)]
|
|
(if (< 1 (count s))
|
|
(str (str-upper (subs s 0 1))
|
|
(str-lower (subs s 1)))
|
|
(str-upper s))))
|
|
|
|
(defn lower-case
|
|
[s]
|
|
(str-lower (to-str s)))
|
|
|
|
(defn upper-case
|
|
[s]
|
|
(str-upper (to-str s)))
|
|
|
|
(defn includes?
|
|
[s substr]
|
|
(not (nil? (str-find substr (to-str s)))))
|
|
|
|
(defn join
|
|
|
|
([coll] (str-join coll))
|
|
([separator coll] (str-join coll separator)))
|
|
|
|
(defn replace
|
|
[s match replacement]
|
|
(str-replace-all match replacement (to-str s)))
|
|
|
|
(defn replace-first
|
|
[s match replacement]
|
|
(str-replace match replacement (to-str s)))
|
|
|
|
(defn reverse
|
|
[s]
|
|
(str-reverse-b s))
|
|
|
|
(defn str-reverse
|
|
[s]
|
|
(str-reverse-b s))
|
|
|
|
(defn split
|
|
([s re] (split s re 0))
|
|
([s re limit]
|
|
;; Java Pattern.split semantics: limit > 0 caps the parts (trailing empties
|
|
;; kept); limit < 0 splits fully and keeps trailing empties; limit 0 (the
|
|
;; default) splits fully then drops trailing empty strings — but a no-match
|
|
;; result ([input], the only 1-element case) is returned as-is.
|
|
(let [parts (vec (str-split re s (if (pos? limit) limit nil)))]
|
|
(if (and (zero? limit) (> (count parts) 1))
|
|
(loop [v parts] (if (and (seq v) (= "" (peek v))) (recur (pop v)) v))
|
|
parts))))
|
|
|
|
(defn split-lines
|
|
"Split s on \\n or \\r\\n, returning a vector of lines."
|
|
[s]
|
|
(vec (str-split #"\r?\n" s)))
|
|
|
|
(defn starts-with?
|
|
[s substr]
|
|
(when (nil? substr) (throw (new NullPointerException "substr")))
|
|
(let [s (to-str s)
|
|
slen (count s) slen2 (count substr)]
|
|
(and (>= slen slen2)
|
|
(= (subs s 0 slen2) substr))))
|
|
|
|
(defn ends-with?
|
|
[s substr]
|
|
(when (nil? substr) (throw (new NullPointerException "substr")))
|
|
(let [s (to-str s)
|
|
slen (count s) slen2 (count substr)]
|
|
(and (>= slen slen2)
|
|
(= (subs s (- slen slen2)) substr))))
|
|
|
|
(defn trim
|
|
|
|
[s]
|
|
(str-trim s))
|
|
|
|
(defn triml
|
|
|
|
[s]
|
|
(str-triml s))
|
|
|
|
(defn trimr
|
|
|
|
[s]
|
|
(str-trimr s))
|
|
|
|
(defn escape
|
|
[s cmap]
|
|
(when (nil? s) (throw (new NullPointerException "s")))
|
|
(apply str
|
|
(map (fn [ch]
|
|
(if-let [rep (cmap ch)] rep (str ch)))
|
|
s)))
|
|
|
|
(defn index-of
|
|
"0-based index of the first occurrence of value in s, or nil."
|
|
([s value]
|
|
(str-find value (to-str s)))
|
|
([s value from]
|
|
(let [idx (str-find value (subs (to-str s) from))]
|
|
(when idx (+ from idx)))))
|
|
|
|
(defn last-index-of
|
|
|
|
([s value]
|
|
(let [r (str-reverse-b s) sval (str-reverse-b value)
|
|
idx (str-find sval r)]
|
|
(when idx (- (count s) (+ idx (count value))))))
|
|
([s value from]
|
|
(let [sub (subs s 0 from) r (str-reverse-b sub) sval (str-reverse-b value)
|
|
idx (str-find sval r)]
|
|
(when idx (- from (+ idx (count value)))))))
|
|
|
|
(defn re-quote-replacement
|
|
"Escape special characters (backslash and dollar) in a regex replacement
|
|
string so it is used literally rather than interpreted."
|
|
[replacement]
|
|
(apply str
|
|
(map (fn [ch]
|
|
(let [c (str ch)]
|
|
(if (or (= c "\\") (= c "$")) (str "\\" c) c)))
|
|
(seq replacement))))
|
|
|
|
;; Ported from clojure.string/trim-newline (CharSequence interop replaced with
|
|
;; portable count/subs). Removes all trailing \n or \r characters.
|
|
(defn trim-newline
|
|
"Removes all trailing newline \\n or return \\r characters from
|
|
string. Similar to Perl's chomp."
|
|
[s]
|
|
(loop [index (count s)]
|
|
(if (zero? index)
|
|
""
|
|
(let [c (subs s (dec index) index)]
|
|
(if (or (= c "\n") (= c "\r"))
|
|
(recur (dec index))
|
|
(subs s 0 index))))))
|