Current section

Files

Jump to
clojerl src clj clojure string.clje
Raw

src/clj/clojure/string.clje

; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
(ns ^{:doc "Clojure String utilities
It is poor form to (:use clojure.string). Instead, use require
with :as to specify a prefix, e.g.
(ns your.namespace.here
(:require [clojure.string :as str]))
Design notes for clojure.string:
1. Strings are Erlang binaries (as opposed to sequences). As such, the
string being manipulated is the first argument to a function;
passing nil will result in an error unless documented otherwise.
If you want sequence-y behavior instead, use a sequence.
2. Functions are generally not lazy, and call straight to host
methods where those are available and efficient.
3. Functions take advantage of String implementation details to
write high-performing loop/recurs instead of using higher-order
functions. (This is not idiomatic in general-purpose application
code.)"
:author "Stuart Sierra, Stuart Halloway, David Liebke"}
clojure.string
(:refer-clojure :exclude (replace reverse)))
(set! *warn-on-infer* true)
(defn ^clojerl.String reverse
"Returns s with its characters reversed."
{:added "1.2"}
[^clojerl.IStringable s]
(-> (.str s)
unicode/characters_to_list
lists/reverse
erlang/list_to_binary))
(defn ^clojerl.String re-quote-replacement
"Given a replacement string that you wish to be a literal
replacement for a pattern match in replace or replace-first, do the
necessary escaping of special characters in the replacement."
{:added "1.5"}
[^clojerl.IStringable replacement]
(erlang.util.Regex/quote (when replacement (str replacement))))
(defn- replace-by
[^clojerl.IStringable s re f]
(with-open [buffer (new erlang.io.StringWriter)]
(let [^clojerl.String s (.str s)
append erlang.io.StringWriter/write.2
length (.length s)
matches (re-run re s :global #erl [:capture :all :index])]
(loop [index 0
[[[i len] :as m] & ms] matches]
(if m
(let [m (map (fn [[i len]] (subs s i (+ i len))) m)
m (if (= (count m) 1) (first m) m)]
(append buffer (subs s index i))
(append buffer (f m))
(recur (+ i len) ms))
(do
(append buffer (subs s index length))
(str buffer)))))))
(defn ^clojerl.String replace
"Replaces all instance of match with replacement in s.
match/replacement can be:
string / string
char / char
pattern / (string or function of match).
See also replace-first.
The replacement is literal (i.e. none of its characters are treated
specially) for all cases above except pattern / string.
For pattern / string, $1, $2, etc. in the replacement string are
substituted with the string that matched the corresponding
parenthesized group in the pattern. If you wish your replacement
string r to be used literally, use (re-quote-replacement r) as the
replacement argument.
Example:
(clojure.string/replace \"Almost Pig Latin\" #\"\\b(\\w)(\\w+)\\b\" \"$2$1ay\")
-> \"lmostAay igPay atinLay\""
{:added "1.2"}
[^clojerl.IStringable s match replacement]
(let [^clojerl.String s (.str s)
replace erlang.util.Regex/replace.4
opts #erl(:global)]
(cond
(string? match) (.replace ^clojerl.String s match replacement)
(regex? match) (if (or (string? replacement)
(instance? erlang.io.StringWriter replacement))
(replace match s (str replacement) opts)
(replace-by s match replacement))
:else (throw (clojerl.BadArgumentError. (str "Invalid match arg: " match))))))
(defn- replace-first-by
[^clojerl.String s re f]
(with-open [buffer (new erlang.io.StringWriter)]
(let [append erlang.io.StringWriter/write.2
length (.length s)
[[i len] :as matches] (re-run re s #erl [:capture :all :index])
matches (map (fn [[i len]] (subs s i (+ i len))) matches)
matches (if (= (count matches) 1) (first matches) matches)]
(if i
(do
(append buffer (subs s 0 i))
(append buffer (f matches))
(append buffer (subs s (+ i len) length))
(str buffer))
s))))
(defn- replace-first-str
[^clojerl.String s match replace]
(let [i (.index_of s match)]
(if (= -1 i)
s
(str (subs s 0 i) replace (subs s (+ i (count match)))))))
(defn ^clojerl.String replace-first
"Replaces the first instance of match with replacement in s.
match/replacement can be:
char / char
string / string
pattern / (string or function of match).
See also replace.
The replacement is literal (i.e. none of its characters are treated
specially) for all cases above except pattern / string.
For pattern / string, $1, $2, etc. in the replacement string are
substituted with the string that matched the corresponding
parenthesized group in the pattern. If you wish your replacement
string r to be used literally, use (re-quote-replacement r) as the
replacement argument.
Example:
(clojure.string/replace-first \"swap first two words\"
#\"(\\w+)(\\s+)(\\w+)\" \"$3$2$1\")
-> \"first swap two words\""
{:added "1.2"}
[^clojerl.IStringable s match replacement]
(let [^clojerl.String s (.str s)
replace erlang.util.Regex/replace.4
opts #erl ()]
(cond
(or (string? match)
(instance? erlang.io.StringWriter match))
(replace-first-str s (str match) (str replacement))
(regex? match)
(if (or (string? replacement)
(instance? erlang.io.StringWriter replacement))
(replace match s (str replacement) opts)
(replace-first-by s match replacement))
:else (throw (clojerl.BadArgumentError. (str "Invalid match arg: " match))))))
(defn ^clojerl.String join
"Returns a string of all elements in coll, as returned by (seq coll),
separated by an optional separator."
{:added "1.2"}
([coll]
(apply str coll))
([separator coll]
(.join ^clojerl.String (clj_rt/to_list coll) separator)))
(defn ^clojerl.String upper-case
"Converts string to all upper-case."
{:added "1.2"}
[^clojerl.IStringable s]
(.to_upper ^clojerl.String (.str s)))
(defn ^clojerl.String lower-case
"Converts string to all lower-case."
{:added "1.2"}
[^clojerl.IStringable s]
(.to_lower ^clojerl.String (.str s)))
(defn ^clojerl.String capitalize
"Converts first character of the string to upper-case, all other
characters to lower-case."
{:added "1.2"}
[^clojerl.IStringable s]
(let [^clojerl.String s (.str s)]
(if (< (.length s) 2)
(.to_upper ^clojerl.String s)
(.append ^clojerl.String
(.to_upper ^clojerl.String (.substring s 0 1))
(.to_lower ^clojerl.String (.substring s 1))))))
(defn split
"Splits string on a regular expression. Optional argument limit is
the maximum number of splits. Not lazy. Returns vector of the splits."
{:added "1.2"}
([^clojerl.IStringable s ^erlang.util.Regex re]
(vec (.split re (.str s) #erl (:trim))))
([^clojerl.IStringable s ^erlang.util.Regex re limit]
(vec (.split re (.str s) #erl(:trim :global #erl [:match_limit limit])))))
(defn split-lines
"Splits s on \\n or \\r\\n."
{:added "1.2"}
[s]
(split s #"\r?\n"))
(defn ^clojerl.String trim
"Removes whitespace from both ends of string."
{:added "1.2"}
[^clojerl.IStringable s]
(let [^clojerl.String s (.str s)
len (.length s)]
(loop [rindex len]
(if (zero? rindex)
""
(if (.is_whitespace ^clojerl.String (.char_at 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 (.is_whitespace ^clojerl.String (.char_at s lindex))
(recur (inc lindex))
(subs s lindex rindex))))))))
(defn ^clojerl.String triml
"Removes whitespace from the left side of string."
{:added "1.2"}
[^clojerl.IStringable s]
(let [^clojerl.String s (.str s)
len (.length s)]
(loop [index 0]
(if (= len index)
""
(if (.is_whitespace ^clojerl.String (.char_at s index))
(recur (inc index))
(subs s index len))))))
(defn ^clojerl.String trimr
"Removes whitespace from the right side of string."
{:added "1.2"}
[^clojerl.IStringable s]
(let [^clojerl.String s (.str s)]
(loop [index (.length s)]
(if (zero? index)
""
(if (.is_whitespace ^clojerl.String (.char_at s (dec index)))
(recur (dec index))
(subs s 0 index))))))
(defn ^clojerl.String trim-newline
"Removes all trailing newline \\n or return \\r characters from
string. Similar to Perl's chomp."
{:added "1.2"}
[^clojerl.IStringable s]
(let [^clojerl.String s (.str s)]
(loop [index (.length s)]
(if (zero? index)
""
(let [ch (.char_at s (dec index))]
(if (or (= ch \newline) (= ch \return))
(recur (dec index))
(subs (.str s) 0 index)))))))
(defn blank?
"True if s is nil, empty, or contains only whitespace."
{:added "1.2"}
[^clojerl.IStringable s]
(if s
(let [^clojerl.String s (.str s)
len (.length s)]
(loop [index (int 0)]
(if (= len index)
true
(if (= :whitespace (clj_utils/char_type (binary/at s index)))
(recur (inc index))
false))))
true))
(defn starts-with?
"True if s starts with substr."
{:added "1.8"}
[^clojerl.IStringable s substr]
(.starts_with ^clojerl.String (.str s) substr))
(defn ^clojerl.String escape
"Return a new string, using cmap to escape each character ch
from s as follows:
If (cmap ch) is nil, append ch to the new string.
If (cmap ch) is non-nil, append (str (cmap ch)) instead."
{:added "1.2"}
[^clojerl.IStringable s cmap]
(let [^clojerl.String s (.str s)
length (.length s)
append erlang.io.StringWriter/write.2]
(with-open [buffer (new erlang.io.StringWriter)]
(loop [index 0
buffer buffer]
(if (= length index)
(str buffer)
(let [ch (.char_at s index)]
(if-let [replacement (cmap ch)]
(append buffer replacement)
(append buffer ch))
(recur (inc index) buffer)))))))
(defn index-of
"Return index of value (string or char) in s, optionally searching
forward from from-index. Return nil if value not found."
{:added "1.8"}
([^clojerl.IStringable s value]
(let [result (.index_of ^clojerl.String (.str s) value)]
(if (= result -1)
nil
result)))
([^clojerl.IStringable s value from-index]
(let [result (.index_of ^clojerl.String (.str s) value from-index)]
(if (= result -1)
nil
result))))
(defn last-index-of
"Return last index of value (string or char) in s, optionally
searching backward from from-index. Return nil if value not found."
{:added "1.8"}
([^clojerl.IStringable s value]
(let [result (.last_index_of ^clojerl.String (.str s) value)]
(if (= result -1)
nil
result)))
([^clojerl.IStringable s value from-index]
(let [result (.last_index_of ^clojerl.String (.str s) value from-index)]
(if (= result -1)
nil
result))))
(defn ends-with?
"True if s ends with substr."
{:added "1.8"}
[^clojerl.IStringable s ^clojerl.String substr]
(.ends_with ^clojerl.String (.str s) substr))
(defn includes?
"True if s includes substr."
{:added "1.8"}
[^clojerl.IStringable s ^clojerl.String substr]
(.contains ^clojerl.String (.str s) substr))