aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorStuart Sierra <mail@stuartsierra.com>2010-04-28 14:57:10 -0400
committerStuart Sierra <mail@stuartsierra.com>2010-04-28 15:00:19 -0400
commita1c66df5287776b4397cf3929a5f498fbb34ea32 (patch)
tree3435025d7898c6c1f279c0186ef0eb92289e09ec /src/main
parentaa4142cd93b83b2a28fab35772cb538c707200be (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.
Diffstat (limited to 'src/main')
-rw-r--r--src/main/clojure/clojure/contrib/duck_streams.clj416
-rw-r--r--src/main/clojure/clojure/contrib/java_utils.clj223
-rw-r--r--src/main/clojure/clojure/contrib/seq_utils.clj223
-rw-r--r--src/main/clojure/clojure/contrib/shell_out.clj146
-rw-r--r--src/main/clojure/clojure/contrib/str_utils.clj100
-rw-r--r--src/main/clojure/clojure/contrib/str_utils2.clj373
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))
+