Mercurial > lasercutter
view src/clojure/contrib/duck_streams.clj @ 10:ef7dbbd6452c
added clojure source goodness
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 21 Aug 2010 06:25:44 -0400 |
parents | |
children |
line wrap: on
line source
1 ;;; duck_streams.clj -- duck-typed I/O streams for Clojure3 ;; by Stuart Sierra, http://stuartsierra.com/4 ;; May 13, 20096 ;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use7 ;; and distribution terms for this software are covered by the Eclipse8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)9 ;; which can be found in the file epl-v10.html at the root of this10 ;; distribution. By using this software in any fashion, you are11 ;; agreeing to be bound by the terms of this license. You must not12 ;; remove this notice, or any other, from this software.15 ;; This file defines "duck-typed" I/O utility functions for Clojure.16 ;; The 'reader' and 'writer' functions will open and return an17 ;; instance of java.io.BufferedReader and java.io.PrintWriter,18 ;; respectively, for a variety of argument types -- filenames as19 ;; strings, URLs, java.io.File's, etc. 'reader' even works on http20 ;; URLs.21 ;;22 ;; Note: this is not really "duck typing" as implemented in languages23 ;; like Ruby. A better name would have been "do-what-I-mean-streams"24 ;; or "just-give-me-a-stream", but ducks are funnier.27 ;; CHANGE LOG28 ;;29 ;; July 23, 2010: DEPRECATED in 1.2. Use clojure.java.io instead.30 ;;31 ;; May 13, 2009: added functions to open writers for appending32 ;;33 ;; May 3, 2009: renamed file to file-str, for compatibility with34 ;; clojure.contrib.java-utils. reader/writer no longer use this35 ;; function.36 ;;37 ;; February 16, 2009: (lazy branch) fixed read-lines to work with lazy38 ;; Clojure.39 ;;40 ;; January 10, 2009: added *default-encoding*, so streams are always41 ;; opened as UTF-8.42 ;;43 ;; December 19, 2008: rewrote reader and writer as multimethods; added44 ;; slurp*, file, and read-lines45 ;;46 ;; April 8, 2008: first version48 (ns49 ^{:author "Stuart Sierra",50 :deprecated "1.2"51 :doc "This file defines \"duck-typed\" I/O utility functions for Clojure.52 The 'reader' and 'writer' functions will open and return an53 instance of java.io.BufferedReader and java.io.PrintWriter,54 respectively, for a variety of argument types -- filenames as55 strings, URLs, java.io.File's, etc. 'reader' even works on http56 URLs.58 Note: this is not really \"duck typing\" as implemented in languages59 like Ruby. A better name would have been \"do-what-I-mean-streams\"60 or \"just-give-me-a-stream\", but ducks are funnier."}61 clojure.contrib.duck-streams62 (:refer-clojure :exclude (spit))63 (:import64 (java.io Reader InputStream InputStreamReader PushbackReader65 BufferedReader File PrintWriter OutputStream66 OutputStreamWriter BufferedWriter Writer67 FileInputStream FileOutputStream ByteArrayOutputStream68 StringReader ByteArrayInputStream)69 (java.net URI URL MalformedURLException Socket)))72 (def73 ^{:doc "Name of the default encoding to use when reading & writing.74 Default is UTF-8."75 :tag "java.lang.String"}76 *default-encoding* "UTF-8")78 (def79 ^{:doc "Size, in bytes or characters, of the buffer used when80 copying streams."}81 *buffer-size* 1024)83 (def84 ^{:doc "Type object for a Java primitive byte array."}85 *byte-array-type* (class (make-array Byte/TYPE 0)))88 (defn ^File file-str89 "Concatenates args as strings and returns a java.io.File. Replaces90 all / and \\ with File/separatorChar. Replaces ~ at the start of91 the path with the user.home system property."92 [& args]93 (let [^String s (apply str args)94 s (.replaceAll (re-matcher #"[/\\]" s) File/separator)95 s (if (.startsWith s "~")96 (str (System/getProperty "user.home")97 File/separator (subs s 1))98 s)]99 (File. s)))102 (defmulti ^{:tag BufferedReader103 :doc "Attempts to coerce its argument into an open104 java.io.BufferedReader. Argument may be an instance of Reader,105 BufferedReader, InputStream, File, URI, URL, Socket, or String.107 If argument is a String, it tries to resolve it first as a URI, then108 as a local file name. URIs with a 'file' protocol are converted to109 local file names. Uses *default-encoding* as the text encoding.111 Should be used inside with-open to ensure the Reader is properly112 closed."113 :arglists '([x])}114 reader class)116 (defmethod reader Reader [x]117 (BufferedReader. x))119 (defmethod reader InputStream [^InputStream x]120 (BufferedReader. (InputStreamReader. x *default-encoding*)))122 (defmethod reader File [^File x]123 (reader (FileInputStream. x)))125 (defmethod reader URL [^URL x]126 (reader (if (= "file" (.getProtocol x))127 (FileInputStream. (.getPath x))128 (.openStream x))))130 (defmethod reader URI [^URI x]131 (reader (.toURL x)))133 (defmethod reader String [^String x]134 (try (let [url (URL. x)]135 (reader url))136 (catch MalformedURLException e137 (reader (File. x)))))139 (defmethod reader Socket [^Socket x]140 (reader (.getInputStream x)))142 (defmethod reader :default [x]143 (throw (Exception. (str "Cannot open " (pr-str x) " as a reader."))))146 (def147 ^{:doc "If true, writer and spit will open files in append mode.148 Defaults to false. Use append-writer or append-spit."149 :tag "java.lang.Boolean"}150 *append-to-writer* false)153 (defmulti ^{:tag PrintWriter154 :doc "Attempts to coerce its argument into an open java.io.PrintWriter155 wrapped around a java.io.BufferedWriter. Argument may be an156 instance of Writer, PrintWriter, BufferedWriter, OutputStream, File,157 URI, URL, Socket, or String.159 If argument is a String, it tries to resolve it first as a URI, then160 as a local file name. URIs with a 'file' protocol are converted to161 local file names.163 Should be used inside with-open to ensure the Writer is properly164 closed."165 :arglists '([x])}166 writer class)168 (defn- assert-not-appending []169 (when *append-to-writer*170 (throw (Exception. "Cannot change an open stream to append mode."))))172 (defmethod writer PrintWriter [x]173 (assert-not-appending)174 x)176 (defmethod writer BufferedWriter [^BufferedWriter x]177 (assert-not-appending)178 (PrintWriter. x))180 (defmethod writer Writer [x]181 (assert-not-appending)182 ;; Writer includes sub-classes such as FileWriter183 (PrintWriter. (BufferedWriter. x)))185 (defmethod writer OutputStream [^OutputStream x]186 (assert-not-appending)187 (PrintWriter.188 (BufferedWriter.189 (OutputStreamWriter. x *default-encoding*))))191 (defmethod writer File [^File x]192 (let [stream (FileOutputStream. x *append-to-writer*)]193 (binding [*append-to-writer* false]194 (writer stream))))196 (defmethod writer URL [^URL x]197 (if (= "file" (.getProtocol x))198 (writer (File. (.getPath x)))199 (throw (Exception. (str "Cannot write to non-file URL <" x ">")))))201 (defmethod writer URI [^URI x]202 (writer (.toURL x)))204 (defmethod writer String [^String x]205 (try (let [url (URL. x)]206 (writer url))207 (catch MalformedURLException err208 (writer (File. x)))))210 (defmethod writer Socket [^Socket x]211 (writer (.getOutputStream x)))213 (defmethod writer :default [x]214 (throw (Exception. (str "Cannot open <" (pr-str x) "> as a writer."))))217 (defn append-writer218 "Like writer but opens file for appending. Does not work on streams219 that are already open."220 [x]221 (binding [*append-to-writer* true]222 (writer x)))225 (defn write-lines226 "Writes lines (a seq) to f, separated by newlines. f is opened with227 writer, and automatically closed at the end of the sequence."228 [f lines]229 (with-open [^PrintWriter writer (writer f)]230 (loop [lines lines]231 (when-let [line (first lines)]232 (.write writer (str line))233 (.println writer)234 (recur (rest lines))))))236 (defn read-lines237 "Like clojure.core/line-seq but opens f with reader. Automatically238 closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE."239 [f]240 (let [read-line (fn this [^BufferedReader rdr]241 (lazy-seq242 (if-let [line (.readLine rdr)]243 (cons line (this rdr))244 (.close rdr))))]245 (read-line (reader f))))247 (defn ^String slurp*248 "Like clojure.core/slurp but opens f with reader."249 [f]250 (with-open [^BufferedReader r (reader f)]251 (let [sb (StringBuilder.)]252 (loop [c (.read r)]253 (if (neg? c)254 (str sb)255 (do (.append sb (char c))256 (recur (.read r))))))))258 (defn spit259 "Opposite of slurp. Opens f with writer, writes content, then260 closes f."261 [f content]262 (with-open [^PrintWriter w (writer f)]263 (.print w content)))265 (defn append-spit266 "Like spit but appends to file."267 [f content]268 (with-open [^PrintWriter w (append-writer f)]269 (.print w content)))271 (defn pwd272 "Returns current working directory as a String. (Like UNIX 'pwd'.)273 Note: In Java, you cannot change the current working directory."274 []275 (System/getProperty "user.dir"))279 (defmacro with-out-writer280 "Opens a writer on f, binds it to *out*, and evalutes body.281 Anything printed within body will be written to f."282 [f & body]283 `(with-open [stream# (writer ~f)]284 (binding [*out* stream#]285 ~@body)))287 (defmacro with-out-append-writer288 "Like with-out-writer but appends to file."289 [f & body]290 `(with-open [stream# (append-writer ~f)]291 (binding [*out* stream#]292 ~@body)))294 (defmacro with-in-reader295 "Opens a PushbackReader on f, binds it to *in*, and evaluates body."296 [f & body]297 `(with-open [stream# (PushbackReader. (reader ~f))]298 (binding [*in* stream#]299 ~@body)))301 (defmulti302 ^{:doc "Copies input to output. Returns nil.303 Input may be an InputStream, Reader, File, byte[], or String.304 Output may be an OutputStream, Writer, or File.306 Does not close any streams except those it opens itself307 (on a File).309 Writing a File fails if the parent directory does not exist."310 :arglists '([input output])}311 copy312 (fn [input output] [(type input) (type output)]))314 (defmethod copy [InputStream OutputStream] [^InputStream input ^OutputStream output]315 (let [buffer (make-array Byte/TYPE *buffer-size*)]316 (loop []317 (let [size (.read input buffer)]318 (when (pos? size)319 (do (.write output buffer 0 size)320 (recur)))))))322 (defmethod copy [InputStream Writer] [^InputStream input ^Writer output]323 (let [^"[B" buffer (make-array Byte/TYPE *buffer-size*)]324 (loop []325 (let [size (.read input buffer)]326 (when (pos? size)327 (let [chars (.toCharArray (String. buffer 0 size *default-encoding*))]328 (do (.write output chars)329 (recur))))))))331 (defmethod copy [InputStream File] [^InputStream input ^File output]332 (with-open [out (FileOutputStream. output)]333 (copy input out)))335 (defmethod copy [Reader OutputStream] [^Reader input ^OutputStream output]336 (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)]337 (loop []338 (let [size (.read input buffer)]339 (when (pos? size)340 (let [bytes (.getBytes (String. buffer 0 size) *default-encoding*)]341 (do (.write output bytes)342 (recur))))))))344 (defmethod copy [Reader Writer] [^Reader input ^Writer output]345 (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)]346 (loop []347 (let [size (.read input buffer)]348 (when (pos? size)349 (do (.write output buffer 0 size)350 (recur)))))))352 (defmethod copy [Reader File] [^Reader input ^File output]353 (with-open [out (FileOutputStream. output)]354 (copy input out)))356 (defmethod copy [File OutputStream] [^File input ^OutputStream output]357 (with-open [in (FileInputStream. input)]358 (copy in output)))360 (defmethod copy [File Writer] [^File input ^Writer output]361 (with-open [in (FileInputStream. input)]362 (copy in output)))364 (defmethod copy [File File] [^File input ^File output]365 (with-open [in (FileInputStream. input)366 out (FileOutputStream. output)]367 (copy in out)))369 (defmethod copy [String OutputStream] [^String input ^OutputStream output]370 (copy (StringReader. input) output))372 (defmethod copy [String Writer] [^String input ^Writer output]373 (copy (StringReader. input) output))375 (defmethod copy [String File] [^String input ^File output]376 (copy (StringReader. input) output))378 (defmethod copy [*byte-array-type* OutputStream] [^"[B" input ^OutputStream output]379 (copy (ByteArrayInputStream. input) output))381 (defmethod copy [*byte-array-type* Writer] [^"[B" input ^Writer output]382 (copy (ByteArrayInputStream. input) output))384 (defmethod copy [*byte-array-type* File] [^"[B" input ^Writer output]385 (copy (ByteArrayInputStream. input) output))388 (defn make-parents389 "Creates all parent directories of file."390 [^File file]391 (.mkdirs (.getParentFile file)))393 (defmulti394 ^{:doc "Converts argument into a Java byte array. Argument may be395 a String, File, InputStream, or Reader. If the argument is already396 a byte array, returns it."397 :arglists '([arg])}398 to-byte-array type)400 (defmethod to-byte-array *byte-array-type* [x] x)402 (defmethod to-byte-array String [^String x]403 (.getBytes x *default-encoding*))405 (defmethod to-byte-array File [^File x]406 (with-open [input (FileInputStream. x)407 buffer (ByteArrayOutputStream.)]408 (copy input buffer)409 (.toByteArray buffer)))411 (defmethod to-byte-array InputStream [^InputStream x]412 (let [buffer (ByteArrayOutputStream.)]413 (copy x buffer)414 (.toByteArray buffer)))416 (defmethod to-byte-array Reader [^Reader x]417 (.getBytes (slurp* x) *default-encoding*))