diff options
author | Stuart Sierra <mail@stuartsierra.com> | 2010-08-07 16:41:53 -0400 |
---|---|---|
committer | Stuart Sierra <mail@stuartsierra.com> | 2010-08-07 16:41:53 -0400 |
commit | a6a92b9b3d2bfd9a56e1e5e9cfba706d1aeeaae5 (patch) | |
tree | f1f3da9887dc2dc557df3282b0bcbd4d701ec593 /modules/duck-streams/src | |
parent | e7930c85290f77815cdb00a60604feedfa2d0194 (diff) |
Split all namespaces into sub-modules.
* Examples and tests have not been copied over.
* Clojure test/compile phases are commented out in parent POM.
* May require installing parent POM before full build.
Diffstat (limited to 'modules/duck-streams/src')
-rw-r--r-- | modules/duck-streams/src/main/clojure/clojure/contrib/duck_streams.clj | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/modules/duck-streams/src/main/clojure/clojure/contrib/duck_streams.clj b/modules/duck-streams/src/main/clojure/clojure/contrib/duck_streams.clj new file mode 100644 index 00000000..8164ffb2 --- /dev/null +++ b/modules/duck-streams/src/main/clojure/clojure/contrib/duck_streams.clj @@ -0,0 +1,418 @@ +;;; 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 +;; +;; July 23, 2010: DEPRECATED in 1.2. Use clojure.java.io instead. +;; +;; 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", + :deprecated "1.2" + :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 + (:refer-clojure :exclude (spit)) + (: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*)) + |