diff options
author | Stuart Sierra <mail@stuartsierra.com> | 2010-04-28 14:57:10 -0400 |
---|---|---|
committer | Stuart Sierra <mail@stuartsierra.com> | 2010-04-28 15:00:19 -0400 |
commit | a1c66df5287776b4397cf3929a5f498fbb34ea32 (patch) | |
tree | 3435025d7898c6c1f279c0186ef0eb92289e09ec | |
parent | aa4142cd93b83b2a28fab35772cb538c707200be (diff) |
Copy deleted/renamed namespaces from 1.1 release; refs #79
* For backward compatibility with 1.1 release.
* Namespaces copied: duck-streams, java-utils, seq-utils,
shell-out, str-utils, str-utils2.
* These namespaces can be marked as deprecated.
* They are copied rather than simply aliased because
some behavior has changed.
-rw-r--r-- | src/main/clojure/clojure/contrib/duck_streams.clj | 416 | ||||
-rw-r--r-- | src/main/clojure/clojure/contrib/java_utils.clj | 223 | ||||
-rw-r--r-- | src/main/clojure/clojure/contrib/seq_utils.clj | 223 | ||||
-rw-r--r-- | src/main/clojure/clojure/contrib/shell_out.clj | 146 | ||||
-rw-r--r-- | src/main/clojure/clojure/contrib/str_utils.clj | 100 | ||||
-rw-r--r-- | src/main/clojure/clojure/contrib/str_utils2.clj | 373 |
6 files changed, 1481 insertions, 0 deletions
diff --git a/src/main/clojure/clojure/contrib/duck_streams.clj b/src/main/clojure/clojure/contrib/duck_streams.clj new file mode 100644 index 00000000..027aae4d --- /dev/null +++ b/src/main/clojure/clojure/contrib/duck_streams.clj @@ -0,0 +1,416 @@ +;;; duck_streams.clj -- duck-typed I/O streams for Clojure + +;; by Stuart Sierra, http://stuartsierra.com/ +;; May 13, 2009 + +;; Copyright (c) Stuart Sierra, 2009. 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. + + +;; This file defines "duck-typed" I/O utility functions for Clojure. +;; The 'reader' and 'writer' functions will open and return an +;; instance of java.io.BufferedReader and java.io.PrintWriter, +;; respectively, for a variety of argument types -- filenames as +;; strings, URLs, java.io.File's, etc. 'reader' even works on http +;; URLs. +;; +;; Note: this is not really "duck typing" as implemented in languages +;; like Ruby. A better name would have been "do-what-I-mean-streams" +;; or "just-give-me-a-stream", but ducks are funnier. + + +;; CHANGE LOG +;; +;; May 13, 2009: added functions to open writers for appending +;; +;; May 3, 2009: renamed file to file-str, for compatibility with +;; clojure.contrib.java-utils. reader/writer no longer use this +;; function. +;; +;; February 16, 2009: (lazy branch) fixed read-lines to work with lazy +;; Clojure. +;; +;; January 10, 2009: added *default-encoding*, so streams are always +;; opened as UTF-8. +;; +;; December 19, 2008: rewrote reader and writer as multimethods; added +;; slurp*, file, and read-lines +;; +;; April 8, 2008: first version + + + +(ns + #^{:author "Stuart Sierra", + :doc "This file defines \"duck-typed\" I/O utility functions for Clojure. + The 'reader' and 'writer' functions will open and return an + instance of java.io.BufferedReader and java.io.PrintWriter, + respectively, for a variety of argument types -- filenames as + strings, URLs, java.io.File's, etc. 'reader' even works on http + URLs. + + Note: this is not really \"duck typing\" as implemented in languages + like Ruby. A better name would have been \"do-what-I-mean-streams\" + or \"just-give-me-a-stream\", but ducks are funnier."} + clojure.contrib.duck-streams + (:import + (java.io Reader InputStream InputStreamReader PushbackReader + BufferedReader File PrintWriter OutputStream + OutputStreamWriter BufferedWriter Writer + FileInputStream FileOutputStream ByteArrayOutputStream + StringReader ByteArrayInputStream) + (java.net URI URL MalformedURLException Socket))) + + +(def + #^{:doc "Name of the default encoding to use when reading & writing. + Default is UTF-8." + :tag "java.lang.String"} + *default-encoding* "UTF-8") + +(def + #^{:doc "Size, in bytes or characters, of the buffer used when + copying streams."} + *buffer-size* 1024) + +(def + #^{:doc "Type object for a Java primitive byte array."} + *byte-array-type* (class (make-array Byte/TYPE 0))) + + +(defn #^File file-str + "Concatenates args as strings and returns a java.io.File. Replaces + all / and \\ with File/separatorChar. Replaces ~ at the start of + the path with the user.home system property." + [& args] + (let [#^String s (apply str args) + s (.replaceAll (re-matcher #"[/\\]" s) File/separator) + s (if (.startsWith s "~") + (str (System/getProperty "user.home") + File/separator (subs s 1)) + s)] + (File. s))) + + +(defmulti #^{:tag BufferedReader + :doc "Attempts to coerce its argument into an open + java.io.BufferedReader. Argument may be an instance of Reader, + BufferedReader, InputStream, File, URI, URL, Socket, or String. + + If argument is a String, it tries to resolve it first as a URI, then + as a local file name. URIs with a 'file' protocol are converted to + local file names. Uses *default-encoding* as the text encoding. + + Should be used inside with-open to ensure the Reader is properly + closed." + :arglists '([x])} + reader class) + +(defmethod reader Reader [x] + (BufferedReader. x)) + +(defmethod reader InputStream [#^InputStream x] + (BufferedReader. (InputStreamReader. x *default-encoding*))) + +(defmethod reader File [#^File x] + (reader (FileInputStream. x))) + +(defmethod reader URL [#^URL x] + (reader (if (= "file" (.getProtocol x)) + (FileInputStream. (.getPath x)) + (.openStream x)))) + +(defmethod reader URI [#^URI x] + (reader (.toURL x))) + +(defmethod reader String [#^String x] + (try (let [url (URL. x)] + (reader url)) + (catch MalformedURLException e + (reader (File. x))))) + +(defmethod reader Socket [#^Socket x] + (reader (.getInputStream x))) + +(defmethod reader :default [x] + (throw (Exception. (str "Cannot open " (pr-str x) " as a reader.")))) + + +(def + #^{:doc "If true, writer and spit will open files in append mode. + Defaults to false. Use append-writer or append-spit." + :tag "java.lang.Boolean"} + *append-to-writer* false) + + +(defmulti #^{:tag PrintWriter + :doc "Attempts to coerce its argument into an open java.io.PrintWriter + wrapped around a java.io.BufferedWriter. Argument may be an + instance of Writer, PrintWriter, BufferedWriter, OutputStream, File, + URI, URL, Socket, or String. + + If argument is a String, it tries to resolve it first as a URI, then + as a local file name. URIs with a 'file' protocol are converted to + local file names. + + Should be used inside with-open to ensure the Writer is properly + closed." + :arglists '([x])} + writer class) + +(defn- assert-not-appending [] + (when *append-to-writer* + (throw (Exception. "Cannot change an open stream to append mode.")))) + +(defmethod writer PrintWriter [x] + (assert-not-appending) + x) + +(defmethod writer BufferedWriter [#^BufferedWriter x] + (assert-not-appending) + (PrintWriter. x)) + +(defmethod writer Writer [x] + (assert-not-appending) + ;; Writer includes sub-classes such as FileWriter + (PrintWriter. (BufferedWriter. x))) + +(defmethod writer OutputStream [#^OutputStream x] + (assert-not-appending) + (PrintWriter. + (BufferedWriter. + (OutputStreamWriter. x *default-encoding*)))) + +(defmethod writer File [#^File x] + (let [stream (FileOutputStream. x *append-to-writer*)] + (binding [*append-to-writer* false] + (writer stream)))) + +(defmethod writer URL [#^URL x] + (if (= "file" (.getProtocol x)) + (writer (File. (.getPath x))) + (throw (Exception. (str "Cannot write to non-file URL <" x ">"))))) + +(defmethod writer URI [#^URI x] + (writer (.toURL x))) + +(defmethod writer String [#^String x] + (try (let [url (URL. x)] + (writer url)) + (catch MalformedURLException err + (writer (File. x))))) + +(defmethod writer Socket [#^Socket x] + (writer (.getOutputStream x))) + +(defmethod writer :default [x] + (throw (Exception. (str "Cannot open <" (pr-str x) "> as a writer.")))) + + +(defn append-writer + "Like writer but opens file for appending. Does not work on streams + that are already open." + [x] + (binding [*append-to-writer* true] + (writer x))) + + +(defn write-lines + "Writes lines (a seq) to f, separated by newlines. f is opened with + writer, and automatically closed at the end of the sequence." + [f lines] + (with-open [#^PrintWriter writer (writer f)] + (loop [lines lines] + (when-let [line (first lines)] + (.write writer (str line)) + (.println writer) + (recur (rest lines)))))) + +(defn read-lines + "Like clojure.core/line-seq but opens f with reader. Automatically + closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE." + [f] + (let [read-line (fn this [#^BufferedReader rdr] + (lazy-seq + (if-let [line (.readLine rdr)] + (cons line (this rdr)) + (.close rdr))))] + (read-line (reader f)))) + +(defn #^String slurp* + "Like clojure.core/slurp but opens f with reader." + [f] + (with-open [#^BufferedReader r (reader f)] + (let [sb (StringBuilder.)] + (loop [c (.read r)] + (if (neg? c) + (str sb) + (do (.append sb (char c)) + (recur (.read r)))))))) + +(defn spit + "Opposite of slurp. Opens f with writer, writes content, then + closes f." + [f content] + (with-open [#^PrintWriter w (writer f)] + (.print w content))) + +(defn append-spit + "Like spit but appends to file." + [f content] + (with-open [#^PrintWriter w (append-writer f)] + (.print w content))) + +(defn pwd + "Returns current working directory as a String. (Like UNIX 'pwd'.) + Note: In Java, you cannot change the current working directory." + [] + (System/getProperty "user.dir")) + + + +(defmacro with-out-writer + "Opens a writer on f, binds it to *out*, and evalutes body. + Anything printed within body will be written to f." + [f & body] + `(with-open [stream# (writer ~f)] + (binding [*out* stream#] + ~@body))) + +(defmacro with-out-append-writer + "Like with-out-writer but appends to file." + [f & body] + `(with-open [stream# (append-writer ~f)] + (binding [*out* stream#] + ~@body))) + +(defmacro with-in-reader + "Opens a PushbackReader on f, binds it to *in*, and evaluates body." + [f & body] + `(with-open [stream# (PushbackReader. (reader ~f))] + (binding [*in* stream#] + ~@body))) + +(defmulti + #^{:doc "Copies input to output. Returns nil. + Input may be an InputStream, Reader, File, byte[], or String. + Output may be an OutputStream, Writer, or File. + + Does not close any streams except those it opens itself + (on a File). + + Writing a File fails if the parent directory does not exist." + :arglists '([input output])} + copy + (fn [input output] [(type input) (type output)])) + +(defmethod copy [InputStream OutputStream] [#^InputStream input #^OutputStream output] + (let [buffer (make-array Byte/TYPE *buffer-size*)] + (loop [] + (let [size (.read input buffer)] + (when (pos? size) + (do (.write output buffer 0 size) + (recur))))))) + +(defmethod copy [InputStream Writer] [#^InputStream input #^Writer output] + (let [#^"[B" buffer (make-array Byte/TYPE *buffer-size*)] + (loop [] + (let [size (.read input buffer)] + (when (pos? size) + (let [chars (.toCharArray (String. buffer 0 size *default-encoding*))] + (do (.write output chars) + (recur)))))))) + +(defmethod copy [InputStream File] [#^InputStream input #^File output] + (with-open [out (FileOutputStream. output)] + (copy input out))) + +(defmethod copy [Reader OutputStream] [#^Reader input #^OutputStream output] + (let [#^"[C" buffer (make-array Character/TYPE *buffer-size*)] + (loop [] + (let [size (.read input buffer)] + (when (pos? size) + (let [bytes (.getBytes (String. buffer 0 size) *default-encoding*)] + (do (.write output bytes) + (recur)))))))) + +(defmethod copy [Reader Writer] [#^Reader input #^Writer output] + (let [#^"[C" buffer (make-array Character/TYPE *buffer-size*)] + (loop [] + (let [size (.read input buffer)] + (when (pos? size) + (do (.write output buffer 0 size) + (recur))))))) + +(defmethod copy [Reader File] [#^Reader input #^File output] + (with-open [out (FileOutputStream. output)] + (copy input out))) + +(defmethod copy [File OutputStream] [#^File input #^OutputStream output] + (with-open [in (FileInputStream. input)] + (copy in output))) + +(defmethod copy [File Writer] [#^File input #^Writer output] + (with-open [in (FileInputStream. input)] + (copy in output))) + +(defmethod copy [File File] [#^File input #^File output] + (with-open [in (FileInputStream. input) + out (FileOutputStream. output)] + (copy in out))) + +(defmethod copy [String OutputStream] [#^String input #^OutputStream output] + (copy (StringReader. input) output)) + +(defmethod copy [String Writer] [#^String input #^Writer output] + (copy (StringReader. input) output)) + +(defmethod copy [String File] [#^String input #^File output] + (copy (StringReader. input) output)) + +(defmethod copy [*byte-array-type* OutputStream] [#^"[B" input #^OutputStream output] + (copy (ByteArrayInputStream. input) output)) + +(defmethod copy [*byte-array-type* Writer] [#^"[B" input #^Writer output] + (copy (ByteArrayInputStream. input) output)) + +(defmethod copy [*byte-array-type* File] [#^"[B" input #^Writer output] + (copy (ByteArrayInputStream. input) output)) + + +(defn make-parents + "Creates all parent directories of file." + [#^File file] + (.mkdirs (.getParentFile file))) + +(defmulti + #^{:doc "Converts argument into a Java byte array. Argument may be + a String, File, InputStream, or Reader. If the argument is already + a byte array, returns it." + :arglists '([arg])} + to-byte-array type) + +(defmethod to-byte-array *byte-array-type* [x] x) + +(defmethod to-byte-array String [#^String x] + (.getBytes x *default-encoding*)) + +(defmethod to-byte-array File [#^File x] + (with-open [input (FileInputStream. x) + buffer (ByteArrayOutputStream.)] + (copy input buffer) + (.toByteArray buffer))) + +(defmethod to-byte-array InputStream [#^InputStream x] + (let [buffer (ByteArrayOutputStream.)] + (copy x buffer) + (.toByteArray buffer))) + +(defmethod to-byte-array Reader [#^Reader x] + (.getBytes (slurp* x) *default-encoding*)) + diff --git a/src/main/clojure/clojure/contrib/java_utils.clj b/src/main/clojure/clojure/contrib/java_utils.clj new file mode 100644 index 00000000..e8d93782 --- /dev/null +++ b/src/main/clojure/clojure/contrib/java_utils.clj @@ -0,0 +1,223 @@ +; Copyright (c) Stuart Halloway & Contributors, April 2009. 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. + +; Design goals: +; +; (1) Ease-of-use. These APIs should be convenient. Performance is secondary. +; +; (2) Duck typing. I hate having to think about the difference between +; a string that names a file, and a File. Ditto for a ton of other +; wrapper classes in the Java world (URL, InternetAddress). With these +; APIs you should be able to think about domain equivalence, not type +; equivalence. +; +; (3) No bossiness. I am not marking any of these functions as private; +; the docstrings will tell you the intended usage but do what works for you. +; +; Feedback welcome! +; +; If something in this module violates the principle of least surprise, please +; let me (Stu) and the Clojure community know via the mailing list. +; +; Contributors: +; +; Stuart Halloway +; Stephen C. Gilardi +; Shawn Hoover +; Perry Trolard +; Stuart Sierra + +(ns + #^{:author "Stuart Halloway, Stephen C. Gilardi, Shawn Hoover, Perry Trolard, Stuart Sierra", + :doc "A set of utilties for dealing with Java stuff like files and properties. + + Design goals: + + (1) Ease-of-use. These APIs should be convenient. Performance is secondary. + + (2) Duck typing. I hate having to think about the difference between + a string that names a file, and a File. Ditto for a ton of other + wrapper classes in the Java world (URL, InternetAddress). With these + APIs you should be able to think about domain equivalence, not type + equivalence. + + (3) No bossiness. I am not marking any of these functions as private + the docstrings will tell you the intended usage but do what works for you. + + Feedback welcome! + + If something in this module violates the principle of least surprise, please + let me (Stu) and the Clojure community know via the mailing list. +"} + clojure.contrib.java-utils + (:import [java.io File FileOutputStream] + [java.util Properties] + [java.net URI URL])) + +(defmulti relative-path-string + "Interpret a String or java.io.File as a relative path string. + Building block for clojure.contrib.java-utils/file." + class) + +(defmethod relative-path-string String [#^String s] + (relative-path-string (File. s))) + +(defmethod relative-path-string File [#^File f] + (if (.isAbsolute f) + (throw (IllegalArgumentException. (str f " is not a relative path"))) + (.getPath f))) + +(defmulti #^File as-file + "Interpret a String or a java.io.File as a File. Building block + for clojure.contrib.java-utils/file, which you should prefer + in most cases." + class) +(defmethod as-file String [#^String s] (File. s)) +(defmethod as-file File [f] f) + +(defn #^File file + "Returns a java.io.File from string or file args." + ([arg] + (as-file arg)) + ([parent child] + (File. #^File (as-file parent) #^String (relative-path-string child))) + ([parent child & more] + (reduce file (file parent child) more))) + +(defn as-str + "Like clojure.core/str, but if an argument is a keyword or symbol, + its name will be used instead of its literal representation. + + Example: + (str :foo :bar) ;;=> \":foo:bar\" + (as-str :foo :bar) ;;=> \"foobar\" + + Note that this does not apply to keywords or symbols nested within + data structures; they will be rendered as with str. + + Example: + (str {:foo :bar}) ;;=> \"{:foo :bar}\" + (as-str {:foo :bar}) ;;=> \"{:foo :bar}\" " + ([] "") + ([x] (if (instance? clojure.lang.Named x) + (name x) + (str x))) + ([x & ys] + ((fn [#^StringBuilder sb more] + (if more + (recur (. sb (append (as-str (first more)))) (next more)) + (str sb))) + (new StringBuilder #^String (as-str x)) ys))) + +(defn get-system-property + "Get a system property." + ([stringable] + (System/getProperty (as-str stringable))) + ([stringable default] + (System/getProperty (as-str stringable) default))) + +(defn set-system-properties + "Set some system properties. Nil clears a property." + [settings] + (doseq [[name val] settings] + (if val + (System/setProperty (as-str name) (as-str val)) + (System/clearProperty (as-str name))))) + +(defmacro with-system-properties + "setting => property-name value + + Sets the system properties to the supplied values, executes the body, and + sets the properties back to their original values. Values of nil are + translated to a clearing of the property." + [settings & body] + `(let [settings# ~settings + current# (reduce (fn [coll# k#] + (assoc coll# k# (get-system-property k#))) + {} + (keys settings#))] + (set-system-properties settings#) + (try + ~@body + (finally + (set-system-properties current#))))) + + +; Not there is no corresponding props->map. Just destructure! +(defn #^Properties as-properties + "Convert any seq of pairs to a java.utils.Properties instance. + Uses as-str to convert both keys and values into strings." + {:tag Properties} + [m] + (let [p (Properties.)] + (doseq [[k v] m] + (.setProperty p (as-str k) (as-str v))) + p)) + +(defn read-properties + "Read properties from file-able." + [file-able] + (with-open [f (java.io.FileInputStream. (file file-able))] + (doto (Properties.) + (.load f)))) + +(defn write-properties + "Write properties to file-able." + {:tag Properties} + ([m file-able] (write-properties m file-able nil)) + ([m file-able comments] + (with-open [#^FileOutputStream f (FileOutputStream. (file file-able))] + (doto (as-properties m) + (.store f #^String comments))))) + +(defn delete-file + "Delete file f. Raise an exception if it fails unless silently is true." + [f & [silently]] + (or (.delete (file f)) + silently + (throw (java.io.IOException. (str "Couldn't delete " f))))) + +(defn delete-file-recursively + "Delete file f. If it's a directory, recursively delete all its contents. +Raise an exception if any deletion fails unless silently is true." + [f & [silently]] + (let [f (file f)] + (if (.isDirectory f) + (doseq [child (.listFiles f)] + (delete-file-recursively child silently))) + (delete-file f silently))) + +(defmulti + #^{:doc "Coerces argument (URL, URI, or String) to a java.net.URL." + :arglists '([arg])} + as-url type) + +(defmethod as-url URL [x] x) + +(defmethod as-url URI [#^URI x] (.toURL x)) + +(defmethod as-url String [#^String x] (URL. x)) + +(defmethod as-url File [#^File x] (.toURL x)) + +(defn wall-hack-method + "Calls a private or protected method. + params is a vector of class which correspond to the arguments to the method + obj is nil for static methods, the instance object otherwise + the method name is given as a symbol or a keyword (something Named)" + [class-name method-name params obj & args] + (-> class-name (.getDeclaredMethod (name method-name) (into-array Class params)) + (doto (.setAccessible true)) + (.invoke obj (into-array Object args)))) + +(defn wall-hack-field + "Access to private or protected field." + [class-name field-name obj] + (-> class-name (.getDeclaredField (name field-name)) + (doto (.setAccessible true)) + (.get obj))) diff --git a/src/main/clojure/clojure/contrib/seq_utils.clj b/src/main/clojure/clojure/contrib/seq_utils.clj new file mode 100644 index 00000000..ad913f70 --- /dev/null +++ b/src/main/clojure/clojure/contrib/seq_utils.clj @@ -0,0 +1,223 @@ +;;; seq_utils.clj -- Sequence utilities for Clojure + +;; by Stuart Sierra, http://stuartsierra.com/ +;; last updated March 2, 2009 + +;; Copyright (c) Stuart Sierra, 2008. 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. + + +;; Change Log +;; +;; January 10, 2009 (Stuart Sierra): +;; +;; * BREAKING CHANGE: "includes?" now takes collection as first +;; argument. This is more consistent with Clojure collection +;; functions; see discussion at http://groups.google.com/group/clojure/browse_thread/thread/8b2c8dc96b39ddd7/a8866d34b601ff43 + + +(ns + #^{:author "Stuart Sierra (and others)", + :doc "Sequence utilities for Clojure"} + clojure.contrib.seq-utils + (:import (java.util.concurrent LinkedBlockingQueue TimeUnit) + (java.lang.ref WeakReference))) + + +;; 'flatten' written by Rich Hickey, +;; see http://groups.google.com/group/clojure/msg/385098fabfcaad9b +(defn flatten + "Takes any nested combination of sequential things (lists, vectors, + etc.) and returns their contents as a single, flat sequence. + (flatten nil) returns nil." + [x] + (filter (complement sequential?) + (rest (tree-seq sequential? seq x)))) + +(defn separate + "Returns a vector: + [ (filter f s), (filter (complement f) s) ]" + [f s] + [(filter f s) (filter (complement f) s)]) + +(defn includes? + "Returns true if coll contains something equal (with =) to x, + in linear time." + [coll x] + (if (some (fn [y] (= y x)) coll) + true false)) + +(defn indexed + "Returns a lazy sequence of [index, item] pairs, where items come + from 's' and indexes count up from zero. + + (indexed '(a b c d)) => ([0 a] [1 b] [2 c] [3 d])" + [s] + (map vector (iterate inc 0) s)) + +;; group-by written by Rich Hickey; +;; see http://paste.lisp.org/display/64190 +(defn group-by + "Returns a sorted map of the elements of coll keyed by the result of + f on each element. The value at each key will be a vector of the + corresponding elements, in the order they appeared in coll." + [f coll] + (reduce + (fn [ret x] + (let [k (f x)] + (assoc ret k (conj (get ret k []) x)))) + (sorted-map) coll)) + +;; partition-by originally written by Rich Hickey; +;; modified by Stuart Sierra +(defn partition-by + "Applies f to each value in coll, splitting it each time f returns + a new value. Returns a lazy seq of lazy seqs." + [f coll] + (when-let [s (seq coll)] + (let [fst (first s) + fv (f fst) + run (cons fst (take-while #(= fv (f %)) (rest s)))] + (lazy-seq + (cons run (partition-by f (drop (count run) s))))))) + +(defn frequencies + "Returns a map from distinct items in coll to the number of times + they appear." + [coll] + (reduce (fn [counts x] + (assoc counts x (inc (get counts x 0)))) + {} coll)) + +;; recursive sequence helpers by Christophe Grand +;; see http://clj-me.blogspot.com/2009/01/recursive-seqs.html +(defmacro rec-seq + "Similar to lazy-seq but binds the resulting seq to the supplied + binding-name, allowing for recursive expressions." + [binding-name & body] + `(let [s# (atom nil)] + (reset! s# (lazy-seq (let [~binding-name @s#] ~@body))))) + +(defmacro rec-cat + "Similar to lazy-cat but binds the resulting sequence to the supplied + binding-name, allowing for recursive expressions." + [binding-name & exprs] + `(rec-seq ~binding-name (lazy-cat ~@exprs))) + + +;; reductions by Chris Houser +;; see http://groups.google.com/group/clojure/browse_thread/thread/3edf6e82617e18e0/58d9e319ad92aa5f?#58d9e319ad92aa5f +(defn reductions + "Returns a lazy seq of the intermediate values of the reduction (as + per reduce) of coll by f, starting with init." + ([f coll] + (if (seq coll) + (rec-seq self (cons (first coll) (map f self (rest coll)))) + (cons (f) nil))) + ([f init coll] + (rec-seq self (cons init (map f self coll))))) + +(defn rotations + "Returns a lazy seq of all rotations of a seq" + [x] + (if (seq x) + (map + (fn [n _] + (lazy-cat (drop n x) (take n x))) + (iterate inc 0) x) + (list nil))) + +(defn partition-all + "Returns a lazy sequence of lists like clojure.core/partition, but may + include lists with fewer than n items at the end." + ([n coll] + (partition-all n n coll)) + ([n step coll] + (lazy-seq + (when-let [s (seq coll)] + (cons (take n s) (partition-all n step (drop step s))))))) + +(defn shuffle + "Return a random permutation of coll" + [coll] + (let [l (java.util.ArrayList. coll)] + (java.util.Collections/shuffle l) + (seq l))) + +(defn rand-elt + "Return a random element of this seq" + [s] + (nth s (rand-int (count s)))) + + +;; seq-on written by Konrad Hinsen +(defmulti seq-on + "Returns a seq on the object s. Works like the built-in seq but as + a multimethod that can have implementations for new classes and types." + {:arglists '([s])} + type) + +(defmethod seq-on :default + [s] + (seq s)) + + +(defn find-first + "Returns the first item of coll for which (pred item) returns logical true. + Consumes sequences up to the first match, will consume the entire sequence + and return nil if no match is found." + [pred coll] + (first (filter pred coll))) + +; based on work related to Rich Hickey's seque. +; blame Chouser for anything broken or ugly. +(defn fill-queue + "filler-func will be called in another thread with a single arg + 'fill'. filler-func may call fill repeatedly with one arg each + time which will be pushed onto a queue, blocking if needed until + this is possible. fill-queue will return a lazy seq of the values + filler-func has pushed onto the queue, blocking if needed until each + next element becomes available. filler-func's return value is ignored." + ([filler-func & optseq] + (let [opts (apply array-map optseq) + apoll (:alive-poll opts 1) + q (LinkedBlockingQueue. (:queue-size opts 1)) + NIL (Object.) ;nil sentinel since LBQ doesn't support nils + weak-target (Object.) + alive? (WeakReference. weak-target) + fill (fn fill [x] + (if (.get alive?) + (if (.offer q (if (nil? x) NIL x) apoll TimeUnit/SECONDS) + x + (recur x)) + (throw (Exception. "abandoned")))) + f (future + (try + (filler-func fill) + (finally + (.put q q))) ;q itself is eos sentinel + nil)] ; set future's value to nil + ((fn drain [] + weak-target ; force closing over this object + (lazy-seq + (let [x (.take q)] + (if (identical? x q) + @f ;will be nil, touch just to propagate errors + (cons (if (identical? x NIL) nil x) + (drain)))))))))) + +(defn positions + "Returns a lazy sequence containing the positions at which pred + is true for items in coll." + [pred coll] + (for [[idx elt] (indexed coll) :when (pred elt)] idx)) + + + + + diff --git a/src/main/clojure/clojure/contrib/shell_out.clj b/src/main/clojure/clojure/contrib/shell_out.clj new file mode 100644 index 00000000..5e0be467 --- /dev/null +++ b/src/main/clojure/clojure/contrib/shell_out.clj @@ -0,0 +1,146 @@ +; Copyright (c) Chris Houser, Jan 2009. 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. + +; :dir and :env options added by Stuart Halloway + +; Conveniently launch a sub-process providing to its stdin and +; collecting its stdout + +(ns + #^{:author "Chris Houser", + :doc "Conveniently launch a sub-process providing to its stdin and +collecting its stdout"} + clojure.contrib.shell-out + (:import (java.io InputStreamReader OutputStreamWriter))) + +(def *sh-dir* nil) +(def *sh-env* nil) + +(defmacro with-sh-dir [dir & forms] + "Sets the directory for use with sh, see sh for details." + `(binding [*sh-dir* ~dir] + ~@forms)) + +(defmacro with-sh-env [env & forms] + "Sets the environment for use with sh, see sh for details." + `(binding [*sh-env* ~env] + ~@forms)) + +(defn- stream-seq + "Takes an InputStream and returns a lazy seq of integers from the stream." + [stream] + (take-while #(>= % 0) (repeatedly #(.read stream)))) + +(defn- aconcat + "Concatenates arrays of given type." + [type & xs] + (let [target (make-array type (apply + (map count xs)))] + (loop [i 0 idx 0] + (when-let [a (nth xs i nil)] + (System/arraycopy a 0 target idx (count a)) + (recur (inc i) (+ idx (count a))))) + target)) + +(defn- parse-args + "Takes a seq of 'sh' arguments and returns a map of option keywords + to option values." + [args] + (loop [[arg :as args] args opts {:cmd [] :out "UTF-8" :dir *sh-dir* :env *sh-env*}] + (if-not args + opts + (if (keyword? arg) + (recur (nnext args) (assoc opts arg (second args))) + (recur (next args) (update-in opts [:cmd] conj arg)))))) + +(defn- as-env-key [arg] + "Helper so that callers can use symbols, keywords, or strings + when building an environment map." + (cond + (symbol? arg) (name arg) + (keyword? arg) (name arg) + (string? arg) arg)) + +(defn- as-file [arg] + "Helper so that callers can pass a String for the :dir to sh." + (cond + (string? arg) (java.io.File. arg) + (nil? arg) nil + (instance? java.io.File arg) arg)) + +(defn- as-env-string [arg] + "Helper so that callers can pass a Clojure map for the :env to sh." + (cond + (nil? arg) nil + (map? arg) (into-array String (map (fn [[k v]] (str (as-env-key k) "=" v)) arg)) + true arg)) + + +(defn sh + "Passes the given strings to Runtime.exec() to launch a sub-process. + + Options are + + :in may be given followed by a String specifying text to be fed to the + sub-process's stdin. + :out option may be given followed by :bytes or a String. If a String + is given, it will be used as a character encoding name (for + example \"UTF-8\" or \"ISO-8859-1\") to convert the + sub-process's stdout to a String which is returned. + If :bytes is given, the sub-process's stdout will be stored in + a byte array and returned. Defaults to UTF-8. + :return-map + when followed by boolean true, sh returns a map of + :exit => sub-process's exit code + :out => sub-process's stdout (as byte[] or String) + :err => sub-process's stderr (as byte[] or String) + when not given or followed by false, sh returns a single + array or String of the sub-process's stdout followed by its + stderr + :env override the process env with a map (or the underlying Java + String[] if you are a masochist). + :dir override the process dir with a String or java.io.File. + + You can bind :env or :dir for multiple operations using with-sh-env + and with-sh-dir." + [& args] + (let [opts (parse-args args) + proc (.exec (Runtime/getRuntime) + (into-array (:cmd opts)) + (as-env-string (:env opts)) + (as-file (:dir opts)))] + (if (:in opts) + (with-open [osw (OutputStreamWriter. (.getOutputStream proc))] + (.write osw (:in opts))) + (.close (.getOutputStream proc))) + (with-open [stdout (.getInputStream proc) + stderr (.getErrorStream proc)] + (let [[[out err] combine-fn] + (if (= (:out opts) :bytes) + [(for [strm [stdout stderr]] + (into-array Byte/TYPE (map byte (stream-seq strm)))) + #(aconcat Byte/TYPE %1 %2)] + [(for [strm [stdout stderr]] + (apply str (map char (stream-seq + (InputStreamReader. strm (:out opts)))))) + str]) + exit-code (.waitFor proc)] + (if (:return-map opts) + {:exit exit-code :out out :err err} + (combine-fn out err)))))) + +(comment + +(println (sh "ls" "-l")) +(println (sh "ls" "-l" "/no-such-thing")) +(println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n")) +(println (sh "cat" :in "x\u25bax\n")) +(println (sh "echo" "x\u25bax")) +(println (sh "echo" "x\u25bax" :out "ISO-8859-1")) ; reads 4 single-byte chars +(println (sh "cat" "myimage.png" :out :bytes)) ; reads binary file into bytes[] + +) diff --git a/src/main/clojure/clojure/contrib/str_utils.clj b/src/main/clojure/clojure/contrib/str_utils.clj new file mode 100644 index 00000000..02bf7445 --- /dev/null +++ b/src/main/clojure/clojure/contrib/str_utils.clj @@ -0,0 +1,100 @@ +;;; str_utils.clj -- string utilities for Clojure + +;; by Stuart Sierra <mail@stuartsierra.com> +;; April 8, 2008 + +;; Copyright (c) Stuart Sierra, 2008. 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 + #^{:author "Stuart Sierra", + :doc "String utilities for Clojure"} + clojure.contrib.str-utils + (:import (java.util.regex Pattern))) + +(defn re-split + "Splits the string on instances of 'pattern'. Returns a sequence of + strings. Optional 'limit' argument is the maximum number of + splits. Like Perl's 'split'." + ([#^Pattern pattern string] (seq (. pattern (split string)))) + ([#^Pattern pattern string limit] (seq (. pattern (split string limit))))) + +(defn re-partition + "Splits the string into a lazy sequence of substrings, alternating + between substrings that match the patthern and the substrings + between the matches. The sequence always starts with the substring + before the first match, or an empty string if the beginning of the + string matches. + + For example: (re-partition #\"[a-z]+\" \"abc123def\") + + Returns: (\"\" \"abc\" \"123\" \"def\")" + [#^Pattern re string] + (let [m (re-matcher re string)] + ((fn step [prevend] + (lazy-seq + (if (.find m) + (cons (.subSequence string prevend (.start m)) + (cons (re-groups m) + (step (+ (.start m) (count (.group m)))))) + (when (< prevend (.length string)) + (list (.subSequence string prevend (.length string))))))) + 0))) + +(defn re-gsub + "Replaces all instances of 'pattern' in 'string' with + 'replacement'. Like Ruby's 'String#gsub'. + + If (ifn? replacment) is true, the replacement is called with the + match. + " + [#^java.util.regex.Pattern regex replacement #^String string] + (if (ifn? replacement) + (let [parts (vec (re-partition regex string))] + (apply str + (reduce (fn [parts match-idx] + (update-in parts [match-idx] replacement)) + parts (range 1 (count parts) 2)))) + (.. regex (matcher string) (replaceAll replacement)))) + +(defn re-sub + "Replaces the first instance of 'pattern' in 'string' with + 'replacement'. Like Ruby's 'String#sub'. + + If (ifn? replacement) is true, the replacement is called with + the match. + " + [#^Pattern regex replacement #^String string] + (if (ifn? replacement) + (let [m (re-matcher regex string)] + (if (.find m) + (str (.subSequence string 0 (.start m)) + (replacement (re-groups m)) + (.subSequence string (.end m) (.length string))) + string)) + (.. regex (matcher string) (replaceFirst replacement)))) + + +(defn str-join + "Returns a string of all elements in 'sequence', separated by + 'separator'. Like Perl's 'join'." + [separator sequence] + (apply str (interpose separator sequence))) + + +(defn chop + "Removes the last character of string." + [s] + (subs s 0 (dec (count s)))) + +(defn chomp + "Removes all trailing newline \\n or return \\r characters from + string. Note: String.trim() is similar and faster." + [s] + (re-sub #"[\r\n]+$" "" s)) diff --git a/src/main/clojure/clojure/contrib/str_utils2.clj b/src/main/clojure/clojure/contrib/str_utils2.clj new file mode 100644 index 00000000..d49351ac --- /dev/null +++ b/src/main/clojure/clojure/contrib/str_utils2.clj @@ -0,0 +1,373 @@ +;;; str_utils2.clj -- functional string utilities for Clojure + +;; by Stuart Sierra, http://stuartsierra.com/ +;; August 19, 2009 + +;; Copyright (c) Stuart Sierra, 2009. 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 #^{:author "Stuart Sierra" + :doc "This is a library of string manipulation functions. It + is intented as a replacement for clojure.contrib.str-utils. + + You cannot (use 'clojure.contrib.str-utils2) because it defines + functions with the same names as functions in clojure.core. + Instead, do (require '[clojure.contrib.str-utils2 :as s]) + or something similar. + + Goals: + 1. Be functional + 2. String argument first, to work with -> + 3. Performance linear in string length + + Some ideas are borrowed from + http://github.com/francoisdevlin/devlinsf-clojure-utils/"} + clojure.contrib.str-utils2 + (:refer-clojure :exclude (take replace drop butlast partition + contains? get repeat reverse partial)) + (:import (java.util.regex Pattern))) + + +(defmacro dochars + "bindings => [name string] + + Repeatedly executes body, with name bound to each character in + string. Does NOT handle Unicode supplementary characters (above + U+FFFF)." + [bindings & body] + (assert (vector bindings)) + (assert (= 2 (count bindings))) + ;; This seems to be the fastest way to iterate over characters. + `(let [#^String s# ~(second bindings)] + (dotimes [i# (.length s#)] + (let [~(first bindings) (.charAt s# i#)] + ~@body)))) + + +(defmacro docodepoints + "bindings => [name string] + + Repeatedly executes body, with name bound to the integer code point + of each Unicode character in the string. Handles Unicode + supplementary characters (above U+FFFF) correctly." + [bindings & body] + (assert (vector bindings)) + (assert (= 2 (count bindings))) + (let [character (first bindings) + string (second bindings)] + `(let [#^String s# ~string + len# (.length s#)] + (loop [i# 0] + (when (< i# len#) + (let [~character (.charAt s# i#)] + (if (Character/isHighSurrogate ~character) + (let [~character (.codePointAt s# i#)] + ~@body + (recur (+ 2 i#))) + (let [~character (int ~character)] + ~@body + (recur (inc i#)))))))))) + +(defn codepoints + "Returns a sequence of integer Unicode code points in s. Handles + Unicode supplementary characters (above U+FFFF) correctly." + [#^String s] + (let [len (.length s) + f (fn thisfn [#^String s i] + (when (< i len) + (let [c (.charAt s i)] + (if (Character/isHighSurrogate c) + (cons (.codePointAt s i) (thisfn s (+ 2 i))) + (cons (int c) (thisfn s (inc i)))))))] + (lazy-seq (f s 0)))) + +(defn #^String escape + "Returns a new String by applying cmap (a function or a map) to each + character in s. If cmap returns nil, the original character is + added to the output unchanged." + [#^String s cmap] + (let [buffer (StringBuilder. (.length s))] + (dochars [c s] + (if-let [r (cmap c)] + (.append buffer r) + (.append buffer c))) + (.toString buffer))) + +(defn blank? + "True if s is nil, empty, or contains only whitespace." + [#^String s] + (every? (fn [#^Character c] (Character/isWhitespace c)) s)) + +(defn #^String take + "Take first n characters from s, up to the length of s. + + Note the argument order is the opposite of clojure.core/take; this + is to keep the string as the first argument for use with ->" + [#^String s n] + (if (< (count s) n) + s + (.substring s 0 n))) + +(defn #^String drop + "Drops first n characters from s. Returns an empty string if n is + greater than the length of s. + + Note the argument order is the opposite of clojure.core/drop; this + is to keep the string as the first argument for use with ->" + [#^String s n] + (if (< (count s) n) + "" + (.substring s n))) + +(defn #^String butlast + "Returns s without the last n characters. Returns an empty string + if n is greater than the length of s. + + Note the argument order is the opposite of clojure.core/butlast; + this is to keep the string as the first argument for use with ->" + [#^String s n] + (if (< (count s) n) + "" + (.substring s 0 (- (count s) n)))) + +(defn #^String tail + "Returns the last n characters of s." + [#^String s n] + (if (< (count s) n) + s + (.substring s (- (count s) n)))) + +(defn #^String repeat + "Returns a new String containing s repeated n times." + [#^String s n] + (apply str (clojure.core/repeat n s))) + +(defn #^String reverse + "Returns s with its characters reversed." + [#^String s] + (.toString (.reverse (StringBuilder. s)))) + +(defmulti + #^{:doc "Replaces all instances of pattern in string with replacement. + + Allowed argument types for pattern and replacement are: + 1. String and String + 2. Character and Character + 3. regex Pattern and String + (Uses java.util.regex.Matcher.replaceAll) + 4. regex Pattern and function + (Calls function with re-groups of each match, uses return + value as replacement.)" + :arglists '([string pattern replacement]) + :tag String} + replace + (fn [#^String string pattern replacement] + [(class pattern) (class replacement)])) + +(defmethod replace [String String] [#^String s #^String a #^String b] + (.replace s a b)) + +(defmethod replace [Character Character] [#^String s #^Character a #^Character b] + (.replace s a b)) + +(defmethod replace [Pattern String] [#^String s re replacement] + (.replaceAll (re-matcher re s) replacement)) + +(defmethod replace [Pattern clojure.lang.IFn] [#^String s re replacement] + (let [m (re-matcher re s)] + (let [buffer (StringBuffer. (.length s))] + (loop [] + (if (.find m) + (do (.appendReplacement m buffer (replacement (re-groups m))) + (recur)) + (do (.appendTail m buffer) + (.toString buffer))))))) + +(defmulti + #^{:doc "Replaces the first instance of pattern in s with replacement. + + Allowed argument types for pattern and replacement are: + 1. String and String + 2. regex Pattern and String + (Uses java.util.regex.Matcher.replaceAll) + 3. regex Pattern and function +" + :arglists '([s pattern replacement]) + :tag String} + replace-first + (fn [s pattern replacement] + [(class pattern) (class replacement)])) + +(defmethod replace-first [String String] [#^String s pattern replacement] + (.replaceFirst (re-matcher (Pattern/quote pattern) s) replacement)) + +(defmethod replace-first [Pattern String] [#^String s re replacement] + (.replaceFirst (re-matcher re s) replacement)) + +(defmethod replace-first [Pattern clojure.lang.IFn] [#^String s #^Pattern re f] + (let [m (re-matcher re s)] + (let [buffer (StringBuffer.)] + (if (.find m) + (let [rep (f (re-groups m))] + (.appendReplacement m buffer rep) + (.appendTail m buffer) + (str buffer)))))) + +(defn partition + "Splits the string into a lazy sequence of substrings, alternating + between substrings that match the patthern and the substrings + between the matches. The sequence always starts with the substring + before the first match, or an empty string if the beginning of the + string matches. + + For example: (partition \"abc123def\" #\"[a-z]+\") + returns: (\"\" \"abc\" \"123\" \"def\")" + [#^String s #^Pattern re] + (let [m (re-matcher re s)] + ((fn step [prevend] + (lazy-seq + (if (.find m) + (cons (.subSequence s prevend (.start m)) + (cons (re-groups m) + (step (+ (.start m) (count (.group m)))))) + (when (< prevend (.length s)) + (list (.subSequence s prevend (.length s))))))) + 0))) + +(defn #^String join + "Returns a string of all elements in coll, separated by + separator. Like Perl's join." + [#^String separator coll] + (apply str (interpose separator coll))) + +(defn #^String chop + "Removes the last character of string, does nothing on a zero-length + string." + [#^String s] + (let [size (count s)] + (if (zero? size) + s + (subs s 0 (dec (count s)))))) + +(defn #^String chomp + "Removes all trailing newline \\n or return \\r characters from + string. Note: String.trim() is similar and faster." + [#^String s] + (replace s #"[\r\n]+$" "")) + +(defn title-case [#^String s] + (throw (Exception. "title-case not implemeted yet"))) + +(defn #^String swap-case + "Changes upper case characters to lower case and vice-versa. + Handles Unicode supplementary characters correctly. Uses the + locale-sensitive String.toUpperCase() and String.toLowerCase() + methods." + [#^String s] + (let [buffer (StringBuilder. (.length s)) + ;; array to make a String from one code point + #^"[I" array (make-array Integer/TYPE 1)] + (docodepoints [c s] + (aset-int array 0 c) + (if (Character/isLowerCase c) + ;; Character.toUpperCase is not locale-sensitive, but + ;; String.toUpperCase is; so we use a String. + (.append buffer (.toUpperCase (String. array 0 1))) + (.append buffer (.toLowerCase (String. array 0 1))))) + (.toString buffer))) + +(defn #^String capitalize + "Converts first character of the string to upper-case, all other + characters to lower-case." + [#^String s] + (if (< (count s) 2) + (.toUpperCase s) + (str (.toUpperCase #^String (subs s 0 1)) + (.toLowerCase #^String (subs s 1))))) + +(defn #^String ltrim + "Removes whitespace from the left side of string." + [#^String s] + (replace s #"^\s+" "")) + +(defn #^String rtrim + "Removes whitespace from the right side of string." + [#^String s] + (replace s #"\s+$" "")) + +(defn split-lines + "Splits s on \\n or \\r\\n." + [#^String s] + (seq (.split #"\r?\n" s))) + +;; borrowed from compojure.str-utils, by James Reeves, EPL 1.0 +(defn #^String map-str + "Apply f to each element of coll, concatenate all results into a + String." + [f coll] + (apply str (map f coll))) + +;; borrowed from compojure.str-utils, by James Reeves, EPL 1.0 +(defn grep + "Filters elements of coll by a regular expression. The String + representation (with str) of each element is tested with re-find." + [re coll] + (filter (fn [x] (re-find re (str x))) coll)) + +(defn partial + "Like clojure.core/partial for functions that take their primary + argument first. + + Takes a function f and its arguments, NOT INCLUDING the first + argument. Returns a new function whose first argument will be the + first argument to f. + + Example: (str-utils2/partial str-utils2/take 2) + ;;=> (fn [s] (str-utils2/take s 2))" + [f & args] + (fn [s & more] (apply f s (concat args more)))) + + +;;; WRAPPERS + +;; The following functions are simple wrappers around java.lang.String +;; functions. They are included here for completeness, and for use +;; when mapping over a collection of strings. + +(defn #^String upper-case + "Converts string to all upper-case." + [#^String s] + (.toUpperCase s)) + +(defn #^String lower-case + "Converts string to all lower-case." + [#^String s] + (.toLowerCase s)) + +(defn split + "Splits string on a regular expression. Optional argument limit is + the maximum number of splits." + ([#^String s #^Pattern re] (seq (.split re s))) + ([#^String s #^Pattern re limit] (seq (.split re s limit)))) + +(defn #^String trim + "Removes whitespace from both ends of string." + [#^String s] + (.trim s)) + +(defn #^String contains? + "True if s contains the substring." + [#^String s substring] + (.contains s substring)) + +(defn #^String get + "Gets the i'th character in string." + [#^String s i] + (.charAt s i)) + |