Mercurial > lasercutter
diff 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 diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/clojure/contrib/duck_streams.clj Sat Aug 21 06:25:44 2010 -0400 1.3 @@ -0,0 +1,418 @@ 1.4 +;;; duck_streams.clj -- duck-typed I/O streams for Clojure 1.5 + 1.6 +;; by Stuart Sierra, http://stuartsierra.com/ 1.7 +;; May 13, 2009 1.8 + 1.9 +;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use 1.10 +;; and distribution terms for this software are covered by the Eclipse 1.11 +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 1.12 +;; which can be found in the file epl-v10.html at the root of this 1.13 +;; distribution. By using this software in any fashion, you are 1.14 +;; agreeing to be bound by the terms of this license. You must not 1.15 +;; remove this notice, or any other, from this software. 1.16 + 1.17 + 1.18 +;; This file defines "duck-typed" I/O utility functions for Clojure. 1.19 +;; The 'reader' and 'writer' functions will open and return an 1.20 +;; instance of java.io.BufferedReader and java.io.PrintWriter, 1.21 +;; respectively, for a variety of argument types -- filenames as 1.22 +;; strings, URLs, java.io.File's, etc. 'reader' even works on http 1.23 +;; URLs. 1.24 +;; 1.25 +;; Note: this is not really "duck typing" as implemented in languages 1.26 +;; like Ruby. A better name would have been "do-what-I-mean-streams" 1.27 +;; or "just-give-me-a-stream", but ducks are funnier. 1.28 + 1.29 + 1.30 +;; CHANGE LOG 1.31 +;; 1.32 +;; July 23, 2010: DEPRECATED in 1.2. Use clojure.java.io instead. 1.33 +;; 1.34 +;; May 13, 2009: added functions to open writers for appending 1.35 +;; 1.36 +;; May 3, 2009: renamed file to file-str, for compatibility with 1.37 +;; clojure.contrib.java-utils. reader/writer no longer use this 1.38 +;; function. 1.39 +;; 1.40 +;; February 16, 2009: (lazy branch) fixed read-lines to work with lazy 1.41 +;; Clojure. 1.42 +;; 1.43 +;; January 10, 2009: added *default-encoding*, so streams are always 1.44 +;; opened as UTF-8. 1.45 +;; 1.46 +;; December 19, 2008: rewrote reader and writer as multimethods; added 1.47 +;; slurp*, file, and read-lines 1.48 +;; 1.49 +;; April 8, 2008: first version 1.50 + 1.51 +(ns 1.52 + ^{:author "Stuart Sierra", 1.53 + :deprecated "1.2" 1.54 + :doc "This file defines \"duck-typed\" I/O utility functions for Clojure. 1.55 + The 'reader' and 'writer' functions will open and return an 1.56 + instance of java.io.BufferedReader and java.io.PrintWriter, 1.57 + respectively, for a variety of argument types -- filenames as 1.58 + strings, URLs, java.io.File's, etc. 'reader' even works on http 1.59 + URLs. 1.60 + 1.61 + Note: this is not really \"duck typing\" as implemented in languages 1.62 + like Ruby. A better name would have been \"do-what-I-mean-streams\" 1.63 + or \"just-give-me-a-stream\", but ducks are funnier."} 1.64 + clojure.contrib.duck-streams 1.65 + (:refer-clojure :exclude (spit)) 1.66 + (:import 1.67 + (java.io Reader InputStream InputStreamReader PushbackReader 1.68 + BufferedReader File PrintWriter OutputStream 1.69 + OutputStreamWriter BufferedWriter Writer 1.70 + FileInputStream FileOutputStream ByteArrayOutputStream 1.71 + StringReader ByteArrayInputStream) 1.72 + (java.net URI URL MalformedURLException Socket))) 1.73 + 1.74 + 1.75 +(def 1.76 + ^{:doc "Name of the default encoding to use when reading & writing. 1.77 + Default is UTF-8." 1.78 + :tag "java.lang.String"} 1.79 + *default-encoding* "UTF-8") 1.80 + 1.81 +(def 1.82 + ^{:doc "Size, in bytes or characters, of the buffer used when 1.83 + copying streams."} 1.84 + *buffer-size* 1024) 1.85 + 1.86 +(def 1.87 + ^{:doc "Type object for a Java primitive byte array."} 1.88 + *byte-array-type* (class (make-array Byte/TYPE 0))) 1.89 + 1.90 + 1.91 +(defn ^File file-str 1.92 + "Concatenates args as strings and returns a java.io.File. Replaces 1.93 + all / and \\ with File/separatorChar. Replaces ~ at the start of 1.94 + the path with the user.home system property." 1.95 + [& args] 1.96 + (let [^String s (apply str args) 1.97 + s (.replaceAll (re-matcher #"[/\\]" s) File/separator) 1.98 + s (if (.startsWith s "~") 1.99 + (str (System/getProperty "user.home") 1.100 + File/separator (subs s 1)) 1.101 + s)] 1.102 + (File. s))) 1.103 + 1.104 + 1.105 +(defmulti ^{:tag BufferedReader 1.106 + :doc "Attempts to coerce its argument into an open 1.107 + java.io.BufferedReader. Argument may be an instance of Reader, 1.108 + BufferedReader, InputStream, File, URI, URL, Socket, or String. 1.109 + 1.110 + If argument is a String, it tries to resolve it first as a URI, then 1.111 + as a local file name. URIs with a 'file' protocol are converted to 1.112 + local file names. Uses *default-encoding* as the text encoding. 1.113 + 1.114 + Should be used inside with-open to ensure the Reader is properly 1.115 + closed." 1.116 + :arglists '([x])} 1.117 + reader class) 1.118 + 1.119 +(defmethod reader Reader [x] 1.120 + (BufferedReader. x)) 1.121 + 1.122 +(defmethod reader InputStream [^InputStream x] 1.123 + (BufferedReader. (InputStreamReader. x *default-encoding*))) 1.124 + 1.125 +(defmethod reader File [^File x] 1.126 + (reader (FileInputStream. x))) 1.127 + 1.128 +(defmethod reader URL [^URL x] 1.129 + (reader (if (= "file" (.getProtocol x)) 1.130 + (FileInputStream. (.getPath x)) 1.131 + (.openStream x)))) 1.132 + 1.133 +(defmethod reader URI [^URI x] 1.134 + (reader (.toURL x))) 1.135 + 1.136 +(defmethod reader String [^String x] 1.137 + (try (let [url (URL. x)] 1.138 + (reader url)) 1.139 + (catch MalformedURLException e 1.140 + (reader (File. x))))) 1.141 + 1.142 +(defmethod reader Socket [^Socket x] 1.143 + (reader (.getInputStream x))) 1.144 + 1.145 +(defmethod reader :default [x] 1.146 + (throw (Exception. (str "Cannot open " (pr-str x) " as a reader.")))) 1.147 + 1.148 + 1.149 +(def 1.150 + ^{:doc "If true, writer and spit will open files in append mode. 1.151 + Defaults to false. Use append-writer or append-spit." 1.152 + :tag "java.lang.Boolean"} 1.153 + *append-to-writer* false) 1.154 + 1.155 + 1.156 +(defmulti ^{:tag PrintWriter 1.157 + :doc "Attempts to coerce its argument into an open java.io.PrintWriter 1.158 + wrapped around a java.io.BufferedWriter. Argument may be an 1.159 + instance of Writer, PrintWriter, BufferedWriter, OutputStream, File, 1.160 + URI, URL, Socket, or String. 1.161 + 1.162 + If argument is a String, it tries to resolve it first as a URI, then 1.163 + as a local file name. URIs with a 'file' protocol are converted to 1.164 + local file names. 1.165 + 1.166 + Should be used inside with-open to ensure the Writer is properly 1.167 + closed." 1.168 + :arglists '([x])} 1.169 + writer class) 1.170 + 1.171 +(defn- assert-not-appending [] 1.172 + (when *append-to-writer* 1.173 + (throw (Exception. "Cannot change an open stream to append mode.")))) 1.174 + 1.175 +(defmethod writer PrintWriter [x] 1.176 + (assert-not-appending) 1.177 + x) 1.178 + 1.179 +(defmethod writer BufferedWriter [^BufferedWriter x] 1.180 + (assert-not-appending) 1.181 + (PrintWriter. x)) 1.182 + 1.183 +(defmethod writer Writer [x] 1.184 + (assert-not-appending) 1.185 + ;; Writer includes sub-classes such as FileWriter 1.186 + (PrintWriter. (BufferedWriter. x))) 1.187 + 1.188 +(defmethod writer OutputStream [^OutputStream x] 1.189 + (assert-not-appending) 1.190 + (PrintWriter. 1.191 + (BufferedWriter. 1.192 + (OutputStreamWriter. x *default-encoding*)))) 1.193 + 1.194 +(defmethod writer File [^File x] 1.195 + (let [stream (FileOutputStream. x *append-to-writer*)] 1.196 + (binding [*append-to-writer* false] 1.197 + (writer stream)))) 1.198 + 1.199 +(defmethod writer URL [^URL x] 1.200 + (if (= "file" (.getProtocol x)) 1.201 + (writer (File. (.getPath x))) 1.202 + (throw (Exception. (str "Cannot write to non-file URL <" x ">"))))) 1.203 + 1.204 +(defmethod writer URI [^URI x] 1.205 + (writer (.toURL x))) 1.206 + 1.207 +(defmethod writer String [^String x] 1.208 + (try (let [url (URL. x)] 1.209 + (writer url)) 1.210 + (catch MalformedURLException err 1.211 + (writer (File. x))))) 1.212 + 1.213 +(defmethod writer Socket [^Socket x] 1.214 + (writer (.getOutputStream x))) 1.215 + 1.216 +(defmethod writer :default [x] 1.217 + (throw (Exception. (str "Cannot open <" (pr-str x) "> as a writer.")))) 1.218 + 1.219 + 1.220 +(defn append-writer 1.221 + "Like writer but opens file for appending. Does not work on streams 1.222 + that are already open." 1.223 + [x] 1.224 + (binding [*append-to-writer* true] 1.225 + (writer x))) 1.226 + 1.227 + 1.228 +(defn write-lines 1.229 + "Writes lines (a seq) to f, separated by newlines. f is opened with 1.230 + writer, and automatically closed at the end of the sequence." 1.231 + [f lines] 1.232 + (with-open [^PrintWriter writer (writer f)] 1.233 + (loop [lines lines] 1.234 + (when-let [line (first lines)] 1.235 + (.write writer (str line)) 1.236 + (.println writer) 1.237 + (recur (rest lines)))))) 1.238 + 1.239 +(defn read-lines 1.240 + "Like clojure.core/line-seq but opens f with reader. Automatically 1.241 + closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE." 1.242 + [f] 1.243 + (let [read-line (fn this [^BufferedReader rdr] 1.244 + (lazy-seq 1.245 + (if-let [line (.readLine rdr)] 1.246 + (cons line (this rdr)) 1.247 + (.close rdr))))] 1.248 + (read-line (reader f)))) 1.249 + 1.250 +(defn ^String slurp* 1.251 + "Like clojure.core/slurp but opens f with reader." 1.252 + [f] 1.253 + (with-open [^BufferedReader r (reader f)] 1.254 + (let [sb (StringBuilder.)] 1.255 + (loop [c (.read r)] 1.256 + (if (neg? c) 1.257 + (str sb) 1.258 + (do (.append sb (char c)) 1.259 + (recur (.read r)))))))) 1.260 + 1.261 +(defn spit 1.262 + "Opposite of slurp. Opens f with writer, writes content, then 1.263 + closes f." 1.264 + [f content] 1.265 + (with-open [^PrintWriter w (writer f)] 1.266 + (.print w content))) 1.267 + 1.268 +(defn append-spit 1.269 + "Like spit but appends to file." 1.270 + [f content] 1.271 + (with-open [^PrintWriter w (append-writer f)] 1.272 + (.print w content))) 1.273 + 1.274 +(defn pwd 1.275 + "Returns current working directory as a String. (Like UNIX 'pwd'.) 1.276 + Note: In Java, you cannot change the current working directory." 1.277 + [] 1.278 + (System/getProperty "user.dir")) 1.279 + 1.280 + 1.281 + 1.282 +(defmacro with-out-writer 1.283 + "Opens a writer on f, binds it to *out*, and evalutes body. 1.284 + Anything printed within body will be written to f." 1.285 + [f & body] 1.286 + `(with-open [stream# (writer ~f)] 1.287 + (binding [*out* stream#] 1.288 + ~@body))) 1.289 + 1.290 +(defmacro with-out-append-writer 1.291 + "Like with-out-writer but appends to file." 1.292 + [f & body] 1.293 + `(with-open [stream# (append-writer ~f)] 1.294 + (binding [*out* stream#] 1.295 + ~@body))) 1.296 + 1.297 +(defmacro with-in-reader 1.298 + "Opens a PushbackReader on f, binds it to *in*, and evaluates body." 1.299 + [f & body] 1.300 + `(with-open [stream# (PushbackReader. (reader ~f))] 1.301 + (binding [*in* stream#] 1.302 + ~@body))) 1.303 + 1.304 +(defmulti 1.305 + ^{:doc "Copies input to output. Returns nil. 1.306 + Input may be an InputStream, Reader, File, byte[], or String. 1.307 + Output may be an OutputStream, Writer, or File. 1.308 + 1.309 + Does not close any streams except those it opens itself 1.310 + (on a File). 1.311 + 1.312 + Writing a File fails if the parent directory does not exist." 1.313 + :arglists '([input output])} 1.314 + copy 1.315 + (fn [input output] [(type input) (type output)])) 1.316 + 1.317 +(defmethod copy [InputStream OutputStream] [^InputStream input ^OutputStream output] 1.318 + (let [buffer (make-array Byte/TYPE *buffer-size*)] 1.319 + (loop [] 1.320 + (let [size (.read input buffer)] 1.321 + (when (pos? size) 1.322 + (do (.write output buffer 0 size) 1.323 + (recur))))))) 1.324 + 1.325 +(defmethod copy [InputStream Writer] [^InputStream input ^Writer output] 1.326 + (let [^"[B" buffer (make-array Byte/TYPE *buffer-size*)] 1.327 + (loop [] 1.328 + (let [size (.read input buffer)] 1.329 + (when (pos? size) 1.330 + (let [chars (.toCharArray (String. buffer 0 size *default-encoding*))] 1.331 + (do (.write output chars) 1.332 + (recur)))))))) 1.333 + 1.334 +(defmethod copy [InputStream File] [^InputStream input ^File output] 1.335 + (with-open [out (FileOutputStream. output)] 1.336 + (copy input out))) 1.337 + 1.338 +(defmethod copy [Reader OutputStream] [^Reader input ^OutputStream output] 1.339 + (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)] 1.340 + (loop [] 1.341 + (let [size (.read input buffer)] 1.342 + (when (pos? size) 1.343 + (let [bytes (.getBytes (String. buffer 0 size) *default-encoding*)] 1.344 + (do (.write output bytes) 1.345 + (recur)))))))) 1.346 + 1.347 +(defmethod copy [Reader Writer] [^Reader input ^Writer output] 1.348 + (let [^"[C" buffer (make-array Character/TYPE *buffer-size*)] 1.349 + (loop [] 1.350 + (let [size (.read input buffer)] 1.351 + (when (pos? size) 1.352 + (do (.write output buffer 0 size) 1.353 + (recur))))))) 1.354 + 1.355 +(defmethod copy [Reader File] [^Reader input ^File output] 1.356 + (with-open [out (FileOutputStream. output)] 1.357 + (copy input out))) 1.358 + 1.359 +(defmethod copy [File OutputStream] [^File input ^OutputStream output] 1.360 + (with-open [in (FileInputStream. input)] 1.361 + (copy in output))) 1.362 + 1.363 +(defmethod copy [File Writer] [^File input ^Writer output] 1.364 + (with-open [in (FileInputStream. input)] 1.365 + (copy in output))) 1.366 + 1.367 +(defmethod copy [File File] [^File input ^File output] 1.368 + (with-open [in (FileInputStream. input) 1.369 + out (FileOutputStream. output)] 1.370 + (copy in out))) 1.371 + 1.372 +(defmethod copy [String OutputStream] [^String input ^OutputStream output] 1.373 + (copy (StringReader. input) output)) 1.374 + 1.375 +(defmethod copy [String Writer] [^String input ^Writer output] 1.376 + (copy (StringReader. input) output)) 1.377 + 1.378 +(defmethod copy [String File] [^String input ^File output] 1.379 + (copy (StringReader. input) output)) 1.380 + 1.381 +(defmethod copy [*byte-array-type* OutputStream] [^"[B" input ^OutputStream output] 1.382 + (copy (ByteArrayInputStream. input) output)) 1.383 + 1.384 +(defmethod copy [*byte-array-type* Writer] [^"[B" input ^Writer output] 1.385 + (copy (ByteArrayInputStream. input) output)) 1.386 + 1.387 +(defmethod copy [*byte-array-type* File] [^"[B" input ^Writer output] 1.388 + (copy (ByteArrayInputStream. input) output)) 1.389 + 1.390 + 1.391 +(defn make-parents 1.392 + "Creates all parent directories of file." 1.393 + [^File file] 1.394 + (.mkdirs (.getParentFile file))) 1.395 + 1.396 +(defmulti 1.397 + ^{:doc "Converts argument into a Java byte array. Argument may be 1.398 + a String, File, InputStream, or Reader. If the argument is already 1.399 + a byte array, returns it." 1.400 + :arglists '([arg])} 1.401 + to-byte-array type) 1.402 + 1.403 +(defmethod to-byte-array *byte-array-type* [x] x) 1.404 + 1.405 +(defmethod to-byte-array String [^String x] 1.406 + (.getBytes x *default-encoding*)) 1.407 + 1.408 +(defmethod to-byte-array File [^File x] 1.409 + (with-open [input (FileInputStream. x) 1.410 + buffer (ByteArrayOutputStream.)] 1.411 + (copy input buffer) 1.412 + (.toByteArray buffer))) 1.413 + 1.414 +(defmethod to-byte-array InputStream [^InputStream x] 1.415 + (let [buffer (ByteArrayOutputStream.)] 1.416 + (copy x buffer) 1.417 + (.toByteArray buffer))) 1.418 + 1.419 +(defmethod to-byte-array Reader [^Reader x] 1.420 + (.getBytes (slurp* x) *default-encoding*)) 1.421 +