Skip to content

Commit 33b03f6

Browse files
authored
String library (#186)
* String library and partial test coverage * Remaining tests
1 parent f483458 commit 33b03f6

File tree

2 files changed

+470
-0
lines changed

2 files changed

+470
-0
lines changed

basilisp/string.lpy

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
(ns basilisp.string
2+
(:import
3+
re
4+
typing))
5+
6+
(defn alpha?
7+
"Return true if s is strictly alphabetic and there is at least one
8+
character.
9+
10+
This function uses Python's underlying str.isalpha and, thus, it
11+
respects unicode."
12+
[s]
13+
(.isalpha s))
14+
15+
(defn alphanumeric?
16+
"Return true if s is strictly alphanumeric and there is at least one
17+
character.
18+
19+
This function uses Python's underlying str.isalnum and, thus, it
20+
respects unicode."
21+
[s]
22+
(.isalnum s))
23+
24+
(defn digits?
25+
"Return true if s is strictly digit characters and there is at least
26+
one character."
27+
[s]
28+
(.isdigit s))
29+
30+
(defn blank?
31+
"Returns true if s is nil, empty, or contains only whitespace."
32+
[s]
33+
(when s
34+
(not (builtins/bool (.strip s)))))
35+
36+
(defn capitalize
37+
"Return a copy of the string with the first character capitalized
38+
and the rest lower case."
39+
[s]
40+
(.capitalize s))
41+
42+
(defn title-case
43+
"Return a copy of the string where the first letter of each word is
44+
capitalized and the rest of the characters in the word are lower
45+
case."
46+
[s]
47+
(.title s))
48+
49+
(defn lower-case
50+
"Return a copy of the string with all characters converted to lower
51+
case."
52+
[s]
53+
(.lower s))
54+
55+
(defn upper-case
56+
"Return a copy of the string with all characters converted to upper
57+
case."
58+
[s]
59+
(.upper s))
60+
61+
(defn ends-with?
62+
"Return true if s ends with the substring substr."
63+
[s suffix]
64+
(.endswith s suffix))
65+
66+
(defn starts-with?
67+
"Return true if s starts with the substring substr."
68+
[s suffix]
69+
(.startswith s suffix))
70+
71+
(defn includes?
72+
"Returns true if substr is contained in s."
73+
[s substr]
74+
(operator/contains s substr))
75+
76+
(defn index-of
77+
"Return the first index of value in s, optionally starting from
78+
from-index. Returns nil if value is not found in s."
79+
([s value]
80+
(index-of s value 0))
81+
([s value from-index]
82+
(let [idx (.find s value from-index)]
83+
(if (= idx -1)
84+
nil
85+
idx))))
86+
87+
(defn last-index-of
88+
"Return the last index of value in s, optionally searching backwards
89+
from from-index. Returns nil if value is not found in s."
90+
([s value]
91+
(last-index-of s value (builtins/len s)))
92+
([s value from-index]
93+
(let [idx (.rfind s value 0 from-index)]
94+
(if (= idx -1)
95+
nil
96+
idx))))
97+
98+
(defn join
99+
"Return a string of the elements in coll joined together, optionally
100+
by a separator."
101+
([coll]
102+
(if (seq coll)
103+
(.join "" (map str coll))
104+
""))
105+
([separator coll]
106+
(if (seq coll)
107+
(.join separator (seq (map str coll)))
108+
"")))
109+
110+
(defn reverse
111+
"Returns a string which is the reverse of s."
112+
[s]
113+
(operator/getitem s (builtins/slice nil nil -1)))
114+
115+
(defn split
116+
"Split a string on a regular expression or another string. Caller may
117+
optionally limit the maximum number of splits with limit. Returns a
118+
vector of the splits."
119+
([s pattern]
120+
(split s pattern nil))
121+
([s pattern limit]
122+
(cond
123+
(instance? typing/Pattern pattern)
124+
(if (= "" (.-pattern pattern))
125+
(split s "" limit)
126+
(vec (re/split pattern s (or (when limit (dec limit)) 0))))
127+
128+
(string? pattern)
129+
(vec
130+
(if (= pattern "")
131+
s
132+
(.split s pattern (or (when limit (dec limit)) -1))))
133+
134+
:else
135+
(throw
136+
(ex-info "String split pattern must be a re.Pattern or str"
137+
{:pattern pattern
138+
:type (builtins/type pattern)})))))
139+
140+
(defn split-lines
141+
"Split s on universal newlines as by Python's str.splitlines."
142+
[s]
143+
(vec (.splitlines s)))
144+
145+
(defn lpad
146+
"Pad s on the left such that the final string length is width.
147+
If the initial string length is less than or equal to width,
148+
return the original string. If a fillchar is specified, pad
149+
with fillchar. Otherwise, use a space."
150+
([s width]
151+
(.rjust s width))
152+
([s width fillchar]
153+
(.rjust s width fillchar)))
154+
155+
(defn rpad
156+
"Pad s on the right such that the final string length is width.
157+
If the initial string length is less than or equal to width,
158+
return the original string. If a fillchar is specified, pad
159+
with fillchar. Otherwise, use a space. "
160+
([s width]
161+
(.ljust s width))
162+
([s width fillchar]
163+
(.ljust s width fillchar)))
164+
165+
(defn re-quote-replacement
166+
"Escape special characters in a regex replacement pattern so they are
167+
interpreted literally, rather than as special characters."
168+
[replacement]
169+
(re/escape replacement))
170+
171+
(defn replace
172+
"Replace all instances of match in s with replacement.
173+
174+
match and replacement can be either:
175+
- string and string
176+
- re.Pattern and (string or function)
177+
178+
If match is a regex pattern and replacement is a function, that function
179+
will be called once for every non-overlapping occurrence of match. The
180+
function should accept one string argument and return a replacement
181+
string."
182+
[s match replacement]
183+
(cond
184+
(and (instance? typing/Pattern match)
185+
(or (string? replacement) (builtins/callable replacement)))
186+
(re/sub match
187+
(if (builtins/callable replacement)
188+
#(replacement (.group % 0))
189+
replacement)
190+
s)
191+
192+
(and (string? match) (string? replacement))
193+
(.replace s match replacement)
194+
195+
:else
196+
(throw
197+
(ex-info "String replace match/replacement must be: (str and str) or (re.Pattern and (str or function))"
198+
{:match match
199+
:match-type (builtins/type match)
200+
:replacement replacement
201+
:replacement-type (builtins/type replacement)}))))
202+
203+
(defn replace-first
204+
"Replace the first instance of match in s with replacement.
205+
206+
match and replacement can be either:
207+
- string and string
208+
- re.Pattern and (string or function)
209+
210+
If match is a regex pattern and replacement is a function, that function
211+
will be called once for every non-overlapping occurrence of match. The
212+
function should accept one string argument and return a replacement
213+
string."
214+
[s match replacement]
215+
(cond
216+
(and (instance? typing/Pattern match)
217+
(or (string? replacement) (builtins/callable replacement)))
218+
(re/sub match
219+
(if (builtins/callable replacement)
220+
#(replacement (.group % 0))
221+
replacement)
222+
s
223+
1)
224+
225+
(and (string? match) (string? replacement))
226+
(.replace s match replacement 1)
227+
228+
:else
229+
(throw
230+
(ex-info "String replace match/replacement must be: (str and str) or (re.Pattern and (str or function))"
231+
{:match match
232+
:match-type (builtins/type match)
233+
:replacement replacement
234+
:replacement-type (builtins/type replacement)}))))
235+
236+
(defn trim
237+
"Trim whitespace off the ends of s."
238+
[s]
239+
(.strip s))
240+
241+
(defn rtrim
242+
"Trim trailing whitespace from s."
243+
[s]
244+
(.rstrip s))
245+
246+
(defn ltrim
247+
"Trim leading whitespace from s."
248+
[s]
249+
(.lstrip s))
250+
251+
(defn trim-newlines
252+
"Trim trailing newline and return characters from s."
253+
[s]
254+
(.rstrip s "\r\n"))

0 commit comments

Comments
 (0)