Skip to content

Commit fde4f64

Browse files
#359 request from Stuart Sierra: split-lines, blank?, escape
Signed-off-by: Stuart Halloway <[email protected]>
1 parent fc8af8c commit fde4f64

File tree

2 files changed

+79
-7
lines changed

2 files changed

+79
-7
lines changed

src/clj/clojure/string.clj

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,29 @@
88

99
(ns ^{:doc "Clojure String utilities
1010
11-
clojure.string adheres to the following design principles:
11+
It is poor form to (:use clojure.string). Instead, use require
12+
with :as to specify a prefix, e.g.
13+
14+
(ns your.namespace.here
15+
(:require '[clojure.string :as str]))
16+
17+
Design notes for clojure.string:
1218
1319
1. Strings are objects (as opposed to sequences). As such, the
1420
string being manipulated is the first argument to a function;
15-
passing nil will result in a NullPointerException. If you
16-
want sequence-y behavior instead, use a sequence.
21+
passing nil will result in a NullPointerException unless
22+
documented otherwise. If you want sequence-y behavior instead,
23+
use a sequence.
1724
1825
2. Functions are generally not lazy, and call straight to host
1926
methods where those are available and efficient.
2027
21-
3. When a function is documented to accept a string argument, it
28+
3. Functions take advantage of String implementation details to
29+
write high-performing loop/recurs instead of using higher-order
30+
functions. (This is not idiomatic in general-purpose application
31+
code.)
32+
33+
4. When a function is documented to accept a string argument, it
2234
will take any implementation of the correct *interface* on the
2335
host platform. In Java, this is CharSequence, which is more
2436
general than String. In ordinary usage you will almost always
@@ -66,7 +78,7 @@ clojure.string adheres to the following design principles:
6678
(instance? CharSequence match) (.replace s ^CharSequence match ^CharSequence replacement)
6779
(instance? Pattern match) (if (instance? CharSequence replacement)
6880
(.replaceAll (re-matcher ^Pattern match s)
69-
(.toString replacement))
81+
(.toString ^CharSequence replacement))
7082
(replace-by s match replacement))
7183
:else (throw (IllegalArgumentException. (str "Invalid match arg: " match))))))
7284

@@ -163,6 +175,12 @@ clojure.string adheres to the following design principles:
163175
([ ^CharSequence s ^Pattern re limit]
164176
(LazilyPersistentVector/createOwning (.split re s limit))))
165177

178+
(defn split-lines
179+
"Splits s on \\n or \\r\\n."
180+
{:added "1.2"}
181+
[^CharSequence s]
182+
(split s #"\r?\n"))
183+
166184
(defn ^String trim
167185
"Removes whitespace from both ends of string."
168186
{:added "1.2"}
@@ -204,4 +222,33 @@ clojure.string adheres to the following design principles:
204222
(recur (dec index))
205223
(.. s (subSequence 0 index) toString))))))
206224

207-
225+
(defn blank?
226+
"True if s is nil, empty, or contains only whitespace."
227+
{:added "1.2"}
228+
[^CharSequence s]
229+
(if s
230+
(loop [index (int 0)]
231+
(if (= (.length s) index)
232+
true
233+
(if (Character/isWhitespace (.charAt s index))
234+
(recur (inc index))
235+
false)))
236+
true))
237+
238+
(defn ^String escape
239+
"Return a new string, using cmap to escape each character ch
240+
from s as follows:
241+
242+
If (cmap ch) is nil, append ch to the new string.
243+
If (cmap ch) is non-nil, append (str (cmap ch)) instead."
244+
{:added "1.2"}
245+
[^CharSequence s cmap]
246+
(loop [index (int 0)
247+
buffer (StringBuilder. (.length s))]
248+
(if (= (.length s) index)
249+
(.toString buffer)
250+
(let [ch (.charAt s index)]
251+
(if-let [replacement (cmap ch)]
252+
(.append buffer replacement)
253+
(.append buffer ch))
254+
(recur (inc index) buffer)))))

test/clojure/test_clojure/string.clj

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,29 @@
9292
"calvino" s/trim [" calvino "]
9393
"calvino " s/triml [" calvino "]
9494
" calvino" s/trimr [" calvino "]
95-
"the end" s/trim-newline ["the end\r\n\r\r\n"]))
95+
"the end" s/trim-newline ["the end\r\n\r\r\n"]
96+
true s/blank? [" "]
97+
["a" "b"] s/split-lines ["a\nb"]
98+
"fa la la" s/escape ["fo lo lo" {\o \a}]))
99+
100+
(deftest t-escape
101+
(is (= "&lt;foo&amp;bar&gt;"
102+
(s/escape "<foo&bar>" {\& "&amp;" \< "&lt;" \> "&gt;"})))
103+
(is (= " \\\"foo\\\" "
104+
(s/escape " \"foo\" " {\" "\\\""})))
105+
(is (= "faabor"
106+
(s/escape "foobar" {\a \o, \o \a}))))
107+
108+
(deftest t-blank
109+
(is (s/blank? nil))
110+
(is (s/blank? ""))
111+
(is (s/blank? " "))
112+
(is (s/blank? " \t \n \r "))
113+
(is (not (s/blank? " foo "))))
114+
115+
(deftest t-split-lines
116+
(let [result (s/split-lines "one\ntwo\r\nthree")]
117+
(is (= ["one" "two" "three"] result))
118+
(is (vector? result)))
119+
(is (= (list "foo") (s/split-lines "foo"))))
120+

0 commit comments

Comments
 (0)