annotate 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
rev   line source
rlm@10 1 ;;; duck_streams.clj -- duck-typed I/O streams for Clojure
rlm@10 2
rlm@10 3 ;; by Stuart Sierra, http://stuartsierra.com/
rlm@10 4 ;; May 13, 2009
rlm@10 5
rlm@10 6 ;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use
rlm@10 7 ;; and distribution terms for this software are covered by the Eclipse
rlm@10 8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
rlm@10 9 ;; which can be found in the file epl-v10.html at the root of this
rlm@10 10 ;; distribution. By using this software in any fashion, you are
rlm@10 11 ;; agreeing to be bound by the terms of this license. You must not
rlm@10 12 ;; remove this notice, or any other, from this software.
rlm@10 13
rlm@10 14
rlm@10 15 ;; This file defines "duck-typed" I/O utility functions for Clojure.
rlm@10 16 ;; The 'reader' and 'writer' functions will open and return an
rlm@10 17 ;; instance of java.io.BufferedReader and java.io.PrintWriter,
rlm@10 18 ;; respectively, for a variety of argument types -- filenames as
rlm@10 19 ;; strings, URLs, java.io.File's, etc. 'reader' even works on http
rlm@10 20 ;; URLs.
rlm@10 21 ;;
rlm@10 22 ;; Note: this is not really "duck typing" as implemented in languages
rlm@10 23 ;; like Ruby. A better name would have been "do-what-I-mean-streams"
rlm@10 24 ;; or "just-give-me-a-stream", but ducks are funnier.
rlm@10 25
rlm@10 26
rlm@10 27 ;; CHANGE LOG
rlm@10 28 ;;
rlm@10 29 ;; July 23, 2010: DEPRECATED in 1.2. Use clojure.java.io instead.
rlm@10 30 ;;
rlm@10 31 ;; May 13, 2009: added functions to open writers for appending
rlm@10 32 ;;
rlm@10 33 ;; May 3, 2009: renamed file to file-str, for compatibility with
rlm@10 34 ;; clojure.contrib.java-utils. reader/writer no longer use this
rlm@10 35 ;; function.
rlm@10 36 ;;
rlm@10 37 ;; February 16, 2009: (lazy branch) fixed read-lines to work with lazy
rlm@10 38 ;; Clojure.
rlm@10 39 ;;
rlm@10 40 ;; January 10, 2009: added *default-encoding*, so streams are always
rlm@10 41 ;; opened as UTF-8.
rlm@10 42 ;;
rlm@10 43 ;; December 19, 2008: rewrote reader and writer as multimethods; added
rlm@10 44 ;; slurp*, file, and read-lines
rlm@10 45 ;;
rlm@10 46 ;; April 8, 2008: first version
rlm@10 47
rlm@10 48 (ns
rlm@10 49 ^{:author "Stuart Sierra",
rlm@10 50 :deprecated "1.2"
rlm@10 51 :doc "This file defines \"duck-typed\" I/O utility functions for Clojure.
rlm@10 52 The 'reader' and 'writer' functions will open and return an
rlm@10 53 instance of java.io.BufferedReader and java.io.PrintWriter,
rlm@10 54 respectively, for a variety of argument types -- filenames as
rlm@10 55 strings, URLs, java.io.File's, etc. 'reader' even works on http
rlm@10 56 URLs.
rlm@10 57
rlm@10 58 Note: this is not really \"duck typing\" as implemented in languages
rlm@10 59 like Ruby. A better name would have been \"do-what-I-mean-streams\"
rlm@10 60 or \"just-give-me-a-stream\", but ducks are funnier."}
rlm@10 61 clojure.contrib.duck-streams
rlm@10 62 (:refer-clojure :exclude (spit))
rlm@10 63 (:import
rlm@10 64 (java.io Reader InputStream InputStreamReader PushbackReader
rlm@10 65 BufferedReader File PrintWriter OutputStream
rlm@10 66 OutputStreamWriter BufferedWriter Writer
rlm@10 67 FileInputStream FileOutputStream ByteArrayOutputStream
rlm@10 68 StringReader ByteArrayInputStream)
rlm@10 69 (java.net URI URL MalformedURLException Socket)))
rlm@10 70
rlm@10 71
rlm@10 72 (def
rlm@10 73 ^{:doc "Name of the default encoding to use when reading & writing.
rlm@10 74 Default is UTF-8."
rlm@10 75 :tag "java.lang.String"}
rlm@10 76 *default-encoding* "UTF-8")
rlm@10 77
rlm@10 78 (def
rlm@10 79 ^{:doc "Size, in bytes or characters, of the buffer used when
rlm@10 80 copying streams."}
rlm@10 81 *buffer-size* 1024)
rlm@10 82
rlm@10 83 (def
rlm@10 84 ^{:doc "Type object for a Java primitive byte array."}
rlm@10 85 *byte-array-type* (class (make-array Byte/TYPE 0)))
rlm@10 86
rlm@10 87
rlm@10 88 (defn ^File file-str
rlm@10 89 "Concatenates args as strings and returns a java.io.File. Replaces
rlm@10 90 all / and \\ with File/separatorChar. Replaces ~ at the start of
rlm@10 91 the path with the user.home system property."
rlm@10 92 [& args]
rlm@10 93 (let [^String s (apply str args)
rlm@10 94 s (.replaceAll (re-matcher #"[/\\]" s) File/separator)
rlm@10 95 s (if (.startsWith s "~")
rlm@10 96 (str (System/getProperty "user.home")
rlm@10 97 File/separator (subs s 1))
rlm@10 98 s)]
rlm@10 99 (File. s)))
rlm@10 100
rlm@10 101
rlm@10 102 (defmulti ^{:tag BufferedReader
rlm@10 103 :doc "Attempts to coerce its argument into an open
rlm@10 104 java.io.BufferedReader. Argument may be an instance of Reader,
rlm@10 105 BufferedReader, InputStream, File, URI, URL, Socket, or String.
rlm@10 106
rlm@10 107 If argument is a String, it tries to resolve it first as a URI, then
rlm@10 108 as a local file name. URIs with a 'file' protocol are converted to
rlm@10 109 local file names. Uses *default-encoding* as the text encoding.
rlm@10 110
rlm@10 111 Should be used inside with-open to ensure the Reader is properly
rlm@10 112 closed."
rlm@10 113 :arglists '([x])}
rlm@10 114 reader class)
rlm@10 115
rlm@10 116 (defmethod reader Reader [x]
rlm@10 117 (BufferedReader. x))
rlm@10 118
rlm@10 119 (defmethod reader InputStream [^InputStream x]
rlm@10 120 (BufferedReader. (InputStreamReader. x *default-encoding*)))
rlm@10 121
rlm@10 122 (defmethod reader File [^File x]
rlm@10 123 (reader (FileInputStream. x)))
rlm@10 124
rlm@10 125 (defmethod reader URL [^URL x]
rlm@10 126 (reader (if (= "file" (.getProtocol x))
rlm@10 127 (FileInputStream. (.getPath x))
rlm@10 128 (.openStream x))))
rlm@10 129
rlm@10 130 (defmethod reader URI [^URI x]
rlm@10 131 (reader (.toURL x)))
rlm@10 132
rlm@10 133 (defmethod reader String [^String x]
rlm@10 134 (try (let [url (URL. x)]
rlm@10 135 (reader url))
rlm@10 136 (catch MalformedURLException e
rlm@10 137 (reader (File. x)))))
rlm@10 138
rlm@10 139 (defmethod reader Socket [^Socket x]
rlm@10 140 (reader (.getInputStream x)))
rlm@10 141
rlm@10 142 (defmethod reader :default [x]
rlm@10 143 (throw (Exception. (str "Cannot open " (pr-str x) " as a reader."))))
rlm@10 144
rlm@10 145
rlm@10 146 (def
rlm@10 147 ^{:doc "If true, writer and spit will open files in append mode.
rlm@10 148 Defaults to false. Use append-writer or append-spit."
rlm@10 149 :tag "java.lang.Boolean"}
rlm@10 150 *append-to-writer* false)
rlm@10 151
rlm@10 152
rlm@10 153 (defmulti ^{:tag PrintWriter
rlm@10 154 :doc "Attempts to coerce its argument into an open java.io.PrintWriter
rlm@10 155 wrapped around a java.io.BufferedWriter. Argument may be an
rlm@10 156 instance of Writer, PrintWriter, BufferedWriter, OutputStream, File,
rlm@10 157 URI, URL, Socket, or String.
rlm@10 158
rlm@10 159 If argument is a String, it tries to resolve it first as a URI, then
rlm@10 160 as a local file name. URIs with a 'file' protocol are converted to
rlm@10 161 local file names.
rlm@10 162
rlm@10 163 Should be used inside with-open to ensure the Writer is properly
rlm@10 164 closed."
rlm@10 165 :arglists '([x])}
rlm@10 166 writer class)
rlm@10 167
rlm@10 168 (defn- assert-not-appending []
rlm@10 169 (when *append-to-writer*
rlm@10 170 (throw (Exception. "Cannot change an open stream to append mode."))))
rlm@10 171
rlm@10 172 (defmethod writer PrintWriter [x]
rlm@10 173 (assert-not-appending)
rlm@10 174 x)
rlm@10 175
rlm@10 176 (defmethod writer BufferedWriter [^BufferedWriter x]
rlm@10 177 (assert-not-appending)
rlm@10 178 (PrintWriter. x))
rlm@10 179
rlm@10 180 (defmethod writer Writer [x]
rlm@10 181 (assert-not-appending)
rlm@10 182 ;; Writer includes sub-classes such as FileWriter
rlm@10 183 (PrintWriter. (BufferedWriter. x)))
rlm@10 184
rlm@10 185 (defmethod writer OutputStream [^OutputStream x]
rlm@10 186 (assert-not-appending)
rlm@10 187 (PrintWriter.
rlm@10 188 (BufferedWriter.
rlm@10 189 (OutputStreamWriter. x *default-encoding*))))
rlm@10 190
rlm@10 191 (defmethod writer File [^File x]
rlm@10 192 (let [stream (FileOutputStream. x *append-to-writer*)]
rlm@10 193 (binding [*append-to-writer* false]
rlm@10 194 (writer stream))))
rlm@10 195
rlm@10 196 (defmethod writer URL [^URL x]
rlm@10 197 (if (= "file" (.getProtocol x))
rlm@10 198 (writer (File. (.getPath x)))
rlm@10 199 (throw (Exception. (str "Cannot write to non-file URL <" x ">")))))
rlm@10 200
rlm@10 201 (defmethod writer URI [^URI x]
rlm@10 202 (writer (.toURL x)))
rlm@10 203
rlm@10 204 (defmethod writer String [^String x]
rlm@10 205 (try (let [url (URL. x)]
rlm@10 206 (writer url))
rlm@10 207 (catch MalformedURLException err
rlm@10 208 (writer (File. x)))))
rlm@10 209
rlm@10 210 (defmethod writer Socket [^Socket x]
rlm@10 211 (writer (.getOutputStream x)))
rlm@10 212
rlm@10 213 (defmethod writer :default [x]
rlm@10 214 (throw (Exception. (str "Cannot open <" (pr-str x) "> as a writer."))))
rlm@10 215
rlm@10 216
rlm@10 217 (defn append-writer
rlm@10 218 "Like writer but opens file for appending. Does not work on streams
rlm@10 219 that are already open."
rlm@10 220 [x]
rlm@10 221 (binding [*append-to-writer* true]
rlm@10 222 (writer x)))
rlm@10 223
rlm@10 224
rlm@10 225 (defn write-lines
rlm@10 226 "Writes lines (a seq) to f, separated by newlines. f is opened with
rlm@10 227 writer, and automatically closed at the end of the sequence."
rlm@10 228 [f lines]
rlm@10 229 (with-open [^PrintWriter writer (writer f)]
rlm@10 230 (loop [lines lines]
rlm@10 231 (when-let [line (first lines)]
rlm@10 232 (.write writer (str line))
rlm@10 233 (.println writer)
rlm@10 234 (recur (rest lines))))))
rlm@10 235
rlm@10 236 (defn read-lines
rlm@10 237 "Like clojure.core/line-seq but opens f with reader. Automatically
rlm@10 238 closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE."
rlm@10 239 [f]
rlm@10 240 (let [read-line (fn this [^BufferedReader rdr]
rlm@10 241 (lazy-seq
rlm@10 242 (if-let [line (.readLine rdr)]
rlm@10 243 (cons line (this rdr))
rlm@10 244 (.close rdr))))]
rlm@10 245 (read-line (reader f))))
rlm@10 246
rlm@10 247 (defn ^String slurp*
rlm@10 248 "Like clojure.core/slurp but opens f with reader."
rlm@10 249 [f]
rlm@10 250 (with-open [^BufferedReader r (reader f)]
rlm@10 251 (let [sb (StringBuilder.)]
rlm@10 252 (loop [c (.read r)]
rlm@10 253 (if (neg? c)
rlm@10 254 (str sb)
rlm@10 255 (do (.append sb (char c))
rlm@10 256 (recur (.read r))))))))
rlm@10 257
rlm@10 258 (defn spit
rlm@10 259 "Opposite of slurp. Opens f with writer, writes content, then
rlm@10 260 closes f."
rlm@10 261 [f content]
rlm@10 262 (with-open [^PrintWriter w (writer f)]
rlm@10 263 (.print w content)))
rlm@10 264
rlm@10 265 (defn append-spit
rlm@10 266 "Like spit but appends to file."
rlm@10 267 [f content]
rlm@10 268 (with-open [^PrintWriter w (append-writer f)]
rlm@10 269 (.print w content)))
rlm@10 270
rlm@10 271 (defn pwd
rlm@10 272 "Returns current working directory as a String. (Like UNIX 'pwd'.)
rlm@10 273 Note: In Java, you cannot change the current working directory."
rlm@10 274 []
rlm@10 275 (System/getProperty "user.dir"))
rlm@10 276
rlm@10 277
rlm@10 278
rlm@10 279 (defmacro with-out-writer
rlm@10 280 "Opens a writer on f, binds it to *out*, and evalutes body.
rlm@10 281 Anything printed within body will be written to f."
rlm@10 282 [f & body]
rlm@10 283 `(with-open [stream# (writer ~f)]
rlm@10 284 (binding [*out* stream#]
rlm@10 285 ~@body)))
rlm@10 286
rlm@10 287 (defmacro with-out-append-writer
rlm@10 288 "Like with-out-writer but appends to file."
rlm@10 289 [f & body]
rlm@10 290 `(with-open [stream# (append-writer ~f)]
rlm@10 291 (binding [*out* stream#]
rlm@10 292 ~@body)))
rlm@10 293
rlm@10 294 (defmacro with-in-reader
rlm@10 295 "Opens a PushbackReader on f, binds it to *in*, and evaluates body."
rlm@10 296 [f & body]
rlm@10 297 `(with-open [stream# (PushbackReader. (reader ~f))]
rlm@10 298 (binding [*in* stream#]
rlm@10 299 ~@body)))
rlm@10 300
rlm@10 301 (defmulti
rlm@10 302 ^{:doc "Copies input to output. Returns nil.
rlm@10 303 Input may be an InputStream, Reader, File, byte[], or String.
rlm@10 304 Output may be an OutputStream, Writer, or File.
rlm@10 305
rlm@10 306 Does not close any streams except those it opens itself
rlm@10 307 (on a File).
rlm@10 308
rlm@10 309 Writing a File fails if the parent directory does not exist."
rlm@10 310 :arglists '([input output])}
rlm@10 311 copy
rlm@10 312 (fn [input output] [(type input) (type output)]))
rlm@10 313
rlm@10 314 (defmethod copy [InputStream OutputStream] [^InputStream input ^OutputStream output]
rlm@10 315 (let [buffer (make-array Byte/TYPE *buffer-size*)]
rlm@10 316 (loop []
rlm@10 317 (let [size (.read input buffer)]
rlm@10 318 (when (pos? size)
rlm@10 319 (do (.write output buffer 0 size)
rlm@10 320 (recur)))))))
rlm@10 321
rlm@10 322 (defmethod copy [InputStream Writer] [^InputStream input ^Writer output]
rlm@10 323 (let [^"[B" buffer (make-array Byte/TYPE *buffer-size*)]
rlm@10 324 (loop []
rlm@10 325 (let [size (.read input buffer)]
rlm@10 326 (when (pos? size)
rlm@10 327 (let [chars (.toCharArray (String. buffer 0 size *default-encoding*))]
rlm@10 328 (do (.write output chars)
rlm@10 329 (recur))))))))
rlm@10 330
rlm@10 331 (defmethod copy [InputStream File] [^InputStream input ^File output]
rlm@10 332 (with-open [out (FileOutputStream. output)]
rlm@10 333 (copy input out)))
rlm@10 334
rlm@10 335 (defmethod copy [Reader OutputStream] [^Reader input ^OutputStream output]
rlm@10 336 (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)]
rlm@10 337 (loop []
rlm@10 338 (let [size (.read input buffer)]
rlm@10 339 (when (pos? size)
rlm@10 340 (let [bytes (.getBytes (String. buffer 0 size) *default-encoding*)]
rlm@10 341 (do (.write output bytes)
rlm@10 342 (recur))))))))
rlm@10 343
rlm@10 344 (defmethod copy [Reader Writer] [^Reader input ^Writer output]
rlm@10 345 (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)]
rlm@10 346 (loop []
rlm@10 347 (let [size (.read input buffer)]
rlm@10 348 (when (pos? size)
rlm@10 349 (do (.write output buffer 0 size)
rlm@10 350 (recur)))))))
rlm@10 351
rlm@10 352 (defmethod copy [Reader File] [^Reader input ^File output]
rlm@10 353 (with-open [out (FileOutputStream. output)]
rlm@10 354 (copy input out)))
rlm@10 355
rlm@10 356 (defmethod copy [File OutputStream] [^File input ^OutputStream output]
rlm@10 357 (with-open [in (FileInputStream. input)]
rlm@10 358 (copy in output)))
rlm@10 359
rlm@10 360 (defmethod copy [File Writer] [^File input ^Writer output]
rlm@10 361 (with-open [in (FileInputStream. input)]
rlm@10 362 (copy in output)))
rlm@10 363
rlm@10 364 (defmethod copy [File File] [^File input ^File output]
rlm@10 365 (with-open [in (FileInputStream. input)
rlm@10 366 out (FileOutputStream. output)]
rlm@10 367 (copy in out)))
rlm@10 368
rlm@10 369 (defmethod copy [String OutputStream] [^String input ^OutputStream output]
rlm@10 370 (copy (StringReader. input) output))
rlm@10 371
rlm@10 372 (defmethod copy [String Writer] [^String input ^Writer output]
rlm@10 373 (copy (StringReader. input) output))
rlm@10 374
rlm@10 375 (defmethod copy [String File] [^String input ^File output]
rlm@10 376 (copy (StringReader. input) output))
rlm@10 377
rlm@10 378 (defmethod copy [*byte-array-type* OutputStream] [^"[B" input ^OutputStream output]
rlm@10 379 (copy (ByteArrayInputStream. input) output))
rlm@10 380
rlm@10 381 (defmethod copy [*byte-array-type* Writer] [^"[B" input ^Writer output]
rlm@10 382 (copy (ByteArrayInputStream. input) output))
rlm@10 383
rlm@10 384 (defmethod copy [*byte-array-type* File] [^"[B" input ^Writer output]
rlm@10 385 (copy (ByteArrayInputStream. input) output))
rlm@10 386
rlm@10 387
rlm@10 388 (defn make-parents
rlm@10 389 "Creates all parent directories of file."
rlm@10 390 [^File file]
rlm@10 391 (.mkdirs (.getParentFile file)))
rlm@10 392
rlm@10 393 (defmulti
rlm@10 394 ^{:doc "Converts argument into a Java byte array. Argument may be
rlm@10 395 a String, File, InputStream, or Reader. If the argument is already
rlm@10 396 a byte array, returns it."
rlm@10 397 :arglists '([arg])}
rlm@10 398 to-byte-array type)
rlm@10 399
rlm@10 400 (defmethod to-byte-array *byte-array-type* [x] x)
rlm@10 401
rlm@10 402 (defmethod to-byte-array String [^String x]
rlm@10 403 (.getBytes x *default-encoding*))
rlm@10 404
rlm@10 405 (defmethod to-byte-array File [^File x]
rlm@10 406 (with-open [input (FileInputStream. x)
rlm@10 407 buffer (ByteArrayOutputStream.)]
rlm@10 408 (copy input buffer)
rlm@10 409 (.toByteArray buffer)))
rlm@10 410
rlm@10 411 (defmethod to-byte-array InputStream [^InputStream x]
rlm@10 412 (let [buffer (ByteArrayOutputStream.)]
rlm@10 413 (copy x buffer)
rlm@10 414 (.toByteArray buffer)))
rlm@10 415
rlm@10 416 (defmethod to-byte-array Reader [^Reader x]
rlm@10 417 (.getBytes (slurp* x) *default-encoding*))
rlm@10 418