diff options
-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")) |