Mercurial > lasercutter
view src/clojure/contrib/io.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 ;;; io.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.BufferedWriter,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: Most functions here are deprecated. Use30 ;; clojure.java.io31 ;;32 ;; May 13, 2009: added functions to open writers for appending33 ;;34 ;; May 3, 2009: renamed file to file-str, for compatibility with35 ;; clojure.contrib.java. reader/writer no longer use this36 ;; function.37 ;;38 ;; February 16, 2009: (lazy branch) fixed read-lines to work with lazy39 ;; Clojure.40 ;;41 ;; January 10, 2009: added *default-encoding*, so streams are always42 ;; opened as UTF-8.43 ;;44 ;; December 19, 2008: rewrote reader and writer as multimethods; added45 ;; slurp*, file, and read-lines46 ;;47 ;; April 8, 2008: first version51 (ns52 ^{:author "Stuart Sierra",53 :doc "This file defines polymorphic I/O utility functions for Clojure.55 The Streams protocol defines reader, writer, input-stream and56 output-stream methods that return BufferedReader, BufferedWriter,57 BufferedInputStream and BufferedOutputStream instances (respectively),58 with default implementations extended to a variety of argument59 types: URLs or filenames as strings, java.io.File's, Sockets, etc."}60 clojure.contrib.io61 (:refer-clojure :exclude (spit))62 (:import63 (java.io Reader InputStream InputStreamReader PushbackReader64 BufferedReader File OutputStream65 OutputStreamWriter BufferedWriter Writer66 FileInputStream FileOutputStream ByteArrayOutputStream67 StringReader ByteArrayInputStream68 BufferedInputStream BufferedOutputStream69 CharArrayReader)70 (java.net URI URL MalformedURLException Socket)))73 (def74 ^{:doc "Name of the default encoding to use when reading & writing.75 Default is UTF-8."76 :tag "java.lang.String"}77 *default-encoding* "UTF-8")79 (def80 ^{:doc "Size, in bytes or characters, of the buffer used when81 copying streams."}82 *buffer-size* 1024)84 (def85 ^{:doc "Type object for a Java primitive byte array."}86 *byte-array-type* (class (make-array Byte/TYPE 0)))88 (def89 ^{:doc "Type object for a Java primitive char array."}90 *char-array-type* (class (make-array Character/TYPE 0)))93 (defn ^File file-str94 "Concatenates args as strings and returns a java.io.File. Replaces95 all / and \\ with File/separatorChar. Replaces ~ at the start of96 the path with the user.home system property."97 [& args]98 (let [^String s (apply str args)99 s (.replace s \\ File/separatorChar)100 s (.replace s \/ File/separatorChar)101 s (if (.startsWith s "~")102 (str (System/getProperty "user.home")103 File/separator (subs s 1))104 s)]105 (File. s)))107 (def108 ^{:doc "If true, writer, output-stream and spit will open files in append mode.109 Defaults to false. Instead of binding this var directly, use append-writer,110 append-output-stream or append-spit."111 :tag "java.lang.Boolean"}112 *append* false)114 (defn- assert-not-appending []115 (when *append*116 (throw (Exception. "Cannot change an open stream to append mode."))))118 ;; @todo -- Both simple and elaborate methods for controlling buffering of119 ;; in the Streams protocol were implemented, considered, and postponed120 ;; see http://groups.google.com/group/clojure-dev/browse_frm/thread/3e39e9b3982f542b121 (defprotocol Streams122 (reader [x]123 "Attempts to coerce its argument into an open java.io.Reader.124 The default implementations of this protocol always return a125 java.io.BufferedReader.127 Default implementations are provided for Reader, BufferedReader,128 InputStream, File, URI, URL, Socket, byte arrays, character arrays,129 and String.131 If argument is a String, it tries to resolve it first as a URI, then132 as a local file name. URIs with a 'file' protocol are converted to133 local file names. If this fails, a final attempt is made to resolve134 the string as a resource on the CLASSPATH.136 Uses *default-encoding* as the text encoding.138 Should be used inside with-open to ensure the Reader is properly139 closed.")140 (writer [x]141 "Attempts to coerce its argument into an open java.io.Writer.142 The default implementations of this protocol always return a143 java.io.BufferedWriter.145 Default implementations are provided for Writer, BufferedWriter,146 OutputStream, File, URI, URL, Socket, and String.148 If the argument is a String, it tries to resolve it first as a URI, then149 as a local file name. URIs with a 'file' protocol are converted to150 local file names.152 Should be used inside with-open to ensure the Writer is properly153 closed.")154 (input-stream [x]155 "Attempts to coerce its argument into an open java.io.InputStream.156 The default implementations of this protocol always return a157 java.io.BufferedInputStream.159 Default implementations are defined for OutputStream, File, URI, URL,160 Socket, byte array, and String arguments.162 If the argument is a String, it tries to resolve it first as a URI, then163 as a local file name. URIs with a 'file' protocol are converted to164 local file names.166 Should be used inside with-open to ensure the InputStream is properly167 closed.")168 (output-stream [x]169 "Attempts to coerce its argument into an open java.io.OutputStream.170 The default implementations of this protocol always return a171 java.io.BufferedOutputStream.173 Default implementations are defined for OutputStream, File, URI, URL,174 Socket, and String arguments.176 If the argument is a String, it tries to resolve it first as a URI, then177 as a local file name. URIs with a 'file' protocol are converted to178 local file names.180 Should be used inside with-open to ensure the OutputStream is181 properly closed."))183 (def default-streams-impl184 {:reader #(reader (input-stream %))185 :writer #(writer (output-stream %))186 :input-stream #(throw (Exception. (str "Cannot open <" (pr-str %) "> as an InputStream.")))187 :output-stream #(throw (Exception. (str "Cannot open <" (pr-str %) "> as an OutputStream.")))})189 (extend File190 Streams191 (assoc default-streams-impl192 :input-stream #(input-stream (FileInputStream. ^File %))193 :output-stream #(let [stream (FileOutputStream. ^File % *append*)]194 (binding [*append* false]195 (output-stream stream)))))196 (extend URL197 Streams198 (assoc default-streams-impl199 :input-stream (fn [^URL x]200 (input-stream (if (= "file" (.getProtocol x))201 (FileInputStream. (.getPath x))202 (.openStream x))))203 :output-stream (fn [^URL x]204 (if (= "file" (.getProtocol x))205 (output-stream (File. (.getPath x)))206 (throw (Exception. (str "Can not write to non-file URL <" x ">")))))))207 (extend URI208 Streams209 (assoc default-streams-impl210 :input-stream #(input-stream (.toURL ^URI %))211 :output-stream #(output-stream (.toURL ^URI %))))212 (extend String213 Streams214 (assoc default-streams-impl215 :input-stream #(try216 (input-stream (URL. %))217 (catch MalformedURLException e218 (input-stream (File. ^String %))))219 :output-stream #(try220 (output-stream (URL. %))221 (catch MalformedURLException err222 (output-stream (File. ^String %))))))223 (extend Socket224 Streams225 (assoc default-streams-impl226 :input-stream #(.getInputStream ^Socket %)227 :output-stream #(output-stream (.getOutputStream ^Socket %))))228 (extend *byte-array-type*229 Streams230 (assoc default-streams-impl :input-stream #(input-stream (ByteArrayInputStream. %))))231 (extend *char-array-type*232 Streams233 (assoc default-streams-impl :reader #(reader (CharArrayReader. %))))234 (extend Object235 Streams236 default-streams-impl)238 (extend Reader239 Streams240 (assoc default-streams-impl :reader #(BufferedReader. %)))241 (extend BufferedReader242 Streams243 (assoc default-streams-impl :reader identity))244 (defn- inputstream->reader245 [^InputStream is]246 (reader (InputStreamReader. is *default-encoding*)))247 (extend InputStream248 Streams249 (assoc default-streams-impl :input-stream #(BufferedInputStream. %)250 :reader inputstream->reader))251 (extend BufferedInputStream252 Streams253 (assoc default-streams-impl254 :input-stream identity255 :reader inputstream->reader))257 (extend Writer258 Streams259 (assoc default-streams-impl :writer #(do (assert-not-appending)260 (BufferedWriter. %))))261 (extend BufferedWriter262 Streams263 (assoc default-streams-impl :writer #(do (assert-not-appending) %)))264 (defn- outputstream->writer265 [^OutputStream os]266 (assert-not-appending)267 (writer (OutputStreamWriter. os *default-encoding*)))268 (extend OutputStream269 Streams270 (assoc default-streams-impl271 :output-stream #(do (assert-not-appending)272 (BufferedOutputStream. %))273 :writer outputstream->writer))274 (extend BufferedOutputStream275 Streams276 (assoc default-streams-impl277 :output-stream #(do (assert-not-appending) %)278 :writer outputstream->writer))280 (defn append-output-stream281 "Like output-stream but opens file for appending. Does not work on streams282 that are already open."283 {:deprecated "1.2"}284 [x]285 (binding [*append* true]286 (output-stream x)))288 (defn append-writer289 "Like writer but opens file for appending. Does not work on streams290 that are already open."291 {:deprecated "1.2"}292 [x]293 (binding [*append* true]294 (writer x)))296 (defn write-lines297 "Writes lines (a seq) to f, separated by newlines. f is opened with298 writer, and automatically closed at the end of the sequence."299 [f lines]300 (with-open [^BufferedWriter writer (writer f)]301 (loop [lines lines]302 (when-let [line (first lines)]303 (.write writer (str line))304 (.newLine writer)305 (recur (rest lines))))))307 (defn read-lines308 "Like clojure.core/line-seq but opens f with reader. Automatically309 closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE."310 [f]311 (let [read-line (fn this [^BufferedReader rdr]312 (lazy-seq313 (if-let [line (.readLine rdr)]314 (cons line (this rdr))315 (.close rdr))))]316 (read-line (reader f))))318 (defn ^String slurp*319 "Like clojure.core/slurp but opens f with reader."320 {:deprecated "1.2"}321 [f]322 (with-open [^BufferedReader r (reader f)]323 (let [sb (StringBuilder.)]324 (loop [c (.read r)]325 (if (neg? c)326 (str sb)327 (do (.append sb (char c))328 (recur (.read r))))))))330 (defn spit331 "Opposite of slurp. Opens f with writer, writes content, then332 closes f."333 {:deprecated "1.2"}334 [f content]335 (with-open [^Writer w (writer f)]336 (.write w content)))338 (defn append-spit339 "Like spit but appends to file."340 {:deprecated "1.2"}341 [f content]342 (with-open [^Writer w (append-writer f)]343 (.write w content)))345 (defn pwd346 "Returns current working directory as a String. (Like UNIX 'pwd'.)347 Note: In Java, you cannot change the current working directory."348 {:deprecated "1.2"}349 []350 (System/getProperty "user.dir"))352 (defmacro with-out-writer353 "Opens a writer on f, binds it to *out*, and evalutes body.354 Anything printed within body will be written to f."355 [f & body]356 `(with-open [stream# (writer ~f)]357 (binding [*out* stream#]358 ~@body)))360 (defmacro with-out-append-writer361 "Like with-out-writer but appends to file."362 {:deprecated "1.2"}363 [f & body]364 `(with-open [stream# (append-writer ~f)]365 (binding [*out* stream#]366 ~@body)))368 (defmacro with-in-reader369 "Opens a PushbackReader on f, binds it to *in*, and evaluates body."370 [f & body]371 `(with-open [stream# (PushbackReader. (reader ~f))]372 (binding [*in* stream#]373 ~@body)))375 (defmulti376 ^{:deprecated "1.2"377 :doc "Copies input to output. Returns nil.378 Input may be an InputStream, Reader, File, byte[], or String.379 Output may be an OutputStream, Writer, or File.381 Does not close any streams except those it opens itself382 (on a File).384 Writing a File fails if the parent directory does not exist."385 :arglists '([input output])}386 copy387 (fn [input output] [(type input) (type output)]))389 (defmethod copy [InputStream OutputStream] [^InputStream input ^OutputStream output]390 (let [buffer (make-array Byte/TYPE *buffer-size*)]391 (loop []392 (let [size (.read input buffer)]393 (when (pos? size)394 (do (.write output buffer 0 size)395 (recur)))))))397 (defmethod copy [InputStream Writer] [^InputStream input ^Writer output]398 (let [^"[B" buffer (make-array Byte/TYPE *buffer-size*)]399 (loop []400 (let [size (.read input buffer)]401 (when (pos? size)402 (let [chars (.toCharArray (String. buffer 0 size *default-encoding*))]403 (do (.write output chars)404 (recur))))))))406 (defmethod copy [InputStream File] [^InputStream input ^File output]407 (with-open [out (FileOutputStream. output)]408 (copy input out)))410 (defmethod copy [Reader OutputStream] [^Reader input ^OutputStream output]411 (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)]412 (loop []413 (let [size (.read input buffer)]414 (when (pos? size)415 (let [bytes (.getBytes (String. buffer 0 size) *default-encoding*)]416 (do (.write output bytes)417 (recur))))))))419 (defmethod copy [Reader Writer] [^Reader input ^Writer output]420 (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)]421 (loop []422 (let [size (.read input buffer)]423 (when (pos? size)424 (do (.write output buffer 0 size)425 (recur)))))))427 (defmethod copy [Reader File] [^Reader input ^File output]428 (with-open [out (FileOutputStream. output)]429 (copy input out)))431 (defmethod copy [File OutputStream] [^File input ^OutputStream output]432 (with-open [in (FileInputStream. input)]433 (copy in output)))435 (defmethod copy [File Writer] [^File input ^Writer output]436 (with-open [in (FileInputStream. input)]437 (copy in output)))439 (defmethod copy [File File] [^File input ^File output]440 (with-open [in (FileInputStream. input)441 out (FileOutputStream. output)]442 (copy in out)))444 (defmethod copy [String OutputStream] [^String input ^OutputStream output]445 (copy (StringReader. input) output))447 (defmethod copy [String Writer] [^String input ^Writer output]448 (copy (StringReader. input) output))450 (defmethod copy [String File] [^String input ^File output]451 (copy (StringReader. input) output))453 (defmethod copy [*char-array-type* OutputStream] [input ^OutputStream output]454 (copy (CharArrayReader. input) output))456 (defmethod copy [*char-array-type* Writer] [input ^Writer output]457 (copy (CharArrayReader. input) output))459 (defmethod copy [*char-array-type* File] [input ^File output]460 (copy (CharArrayReader. input) output))462 (defmethod copy [*byte-array-type* OutputStream] [^"[B" input ^OutputStream output]463 (copy (ByteArrayInputStream. input) output))465 (defmethod copy [*byte-array-type* Writer] [^"[B" input ^Writer output]466 (copy (ByteArrayInputStream. input) output))468 (defmethod copy [*byte-array-type* File] [^"[B" input ^Writer output]469 (copy (ByteArrayInputStream. input) output))471 (defn make-parents472 "Creates all parent directories of file."473 [^File file]474 (.mkdirs (.getParentFile file)))476 (defmulti477 ^{:doc "Converts argument into a Java byte array. Argument may be478 a String, File, InputStream, or Reader. If the argument is already479 a byte array, returns it."480 :arglists '([arg])}481 to-byte-array type)483 (defmethod to-byte-array *byte-array-type* [x] x)485 (defmethod to-byte-array String [^String x]486 (.getBytes x *default-encoding*))488 (defmethod to-byte-array File [^File x]489 (with-open [input (FileInputStream. x)490 buffer (ByteArrayOutputStream.)]491 (copy input buffer)492 (.toByteArray buffer)))494 (defmethod to-byte-array InputStream [^InputStream x]495 (let [buffer (ByteArrayOutputStream.)]496 (copy x buffer)497 (.toByteArray buffer)))499 (defmethod to-byte-array Reader [^Reader x]500 (.getBytes (slurp* x) *default-encoding*))502 (defmulti relative-path-string503 "Interpret a String or java.io.File as a relative path string.504 Building block for clojure.contrib.java/file."505 {:deprecated "1.2"}506 class)508 (defmethod relative-path-string String [^String s]509 (relative-path-string (File. s)))511 (defmethod relative-path-string File [^File f]512 (if (.isAbsolute f)513 (throw (IllegalArgumentException. (str f " is not a relative path")))514 (.getPath f)))516 (defmulti ^File as-file517 "Interpret a String or a java.io.File as a File. Building block518 for clojure.contrib.java/file, which you should prefer519 in most cases."520 {:deprecated "1.2"}521 class)522 (defmethod as-file String [^String s] (File. s))523 (defmethod as-file File [f] f)525 (defn ^File file526 "Returns a java.io.File from string or file args."527 {:deprecated "1.2"}528 ([arg]529 (as-file arg))530 ([parent child]531 (File. ^File (as-file parent) ^String (relative-path-string child)))532 ([parent child & more]533 (reduce file (file parent child) more)))535 (defn delete-file536 "Delete file f. Raise an exception if it fails unless silently is true."537 [f & [silently]]538 (or (.delete (file f))539 silently540 (throw (java.io.IOException. (str "Couldn't delete " f)))))542 (defn delete-file-recursively543 "Delete file f. If it's a directory, recursively delete all its contents.544 Raise an exception if any deletion fails unless silently is true."545 [f & [silently]]546 (let [f (file f)]547 (if (.isDirectory f)548 (doseq [child (.listFiles f)]549 (delete-file-recursively child silently)))550 (delete-file f silently)))552 (defmulti553 ^{:deprecated "1.2"554 :doc "Coerces argument (URL, URI, or String) to a java.net.URL."555 :arglists '([arg])}556 as-url type)558 (defmethod as-url URL [x] x)560 (defmethod as-url URI [^URI x] (.toURL x))562 (defmethod as-url String [^String x] (URL. x))564 (defmethod as-url File [^File x] (.toURL x))