Mercurial > lasercutter
comparison 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 |
comparison
equal
deleted
inserted
replaced
9:35cf337adfcf | 10:ef7dbbd6452c |
---|---|
1 ;;; duck_streams.clj -- duck-typed I/O streams for Clojure | |
2 | |
3 ;; by Stuart Sierra, http://stuartsierra.com/ | |
4 ;; May 13, 2009 | |
5 | |
6 ;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use | |
7 ;; and distribution terms for this software are covered by the Eclipse | |
8 ;; 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 this | |
10 ;; distribution. By using this software in any fashion, you are | |
11 ;; agreeing to be bound by the terms of this license. You must not | |
12 ;; remove this notice, or any other, from this software. | |
13 | |
14 | |
15 ;; This file defines "duck-typed" I/O utility functions for Clojure. | |
16 ;; The 'reader' and 'writer' functions will open and return an | |
17 ;; instance of java.io.BufferedReader and java.io.PrintWriter, | |
18 ;; respectively, for a variety of argument types -- filenames as | |
19 ;; strings, URLs, java.io.File's, etc. 'reader' even works on http | |
20 ;; URLs. | |
21 ;; | |
22 ;; Note: this is not really "duck typing" as implemented in languages | |
23 ;; like Ruby. A better name would have been "do-what-I-mean-streams" | |
24 ;; or "just-give-me-a-stream", but ducks are funnier. | |
25 | |
26 | |
27 ;; CHANGE LOG | |
28 ;; | |
29 ;; July 23, 2010: DEPRECATED in 1.2. Use clojure.java.io instead. | |
30 ;; | |
31 ;; May 13, 2009: added functions to open writers for appending | |
32 ;; | |
33 ;; May 3, 2009: renamed file to file-str, for compatibility with | |
34 ;; clojure.contrib.java-utils. reader/writer no longer use this | |
35 ;; function. | |
36 ;; | |
37 ;; February 16, 2009: (lazy branch) fixed read-lines to work with lazy | |
38 ;; Clojure. | |
39 ;; | |
40 ;; January 10, 2009: added *default-encoding*, so streams are always | |
41 ;; opened as UTF-8. | |
42 ;; | |
43 ;; December 19, 2008: rewrote reader and writer as multimethods; added | |
44 ;; slurp*, file, and read-lines | |
45 ;; | |
46 ;; April 8, 2008: first version | |
47 | |
48 (ns | |
49 ^{: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 an | |
53 instance of java.io.BufferedReader and java.io.PrintWriter, | |
54 respectively, for a variety of argument types -- filenames as | |
55 strings, URLs, java.io.File's, etc. 'reader' even works on http | |
56 URLs. | |
57 | |
58 Note: this is not really \"duck typing\" as implemented in languages | |
59 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-streams | |
62 (:refer-clojure :exclude (spit)) | |
63 (:import | |
64 (java.io Reader InputStream InputStreamReader PushbackReader | |
65 BufferedReader File PrintWriter OutputStream | |
66 OutputStreamWriter BufferedWriter Writer | |
67 FileInputStream FileOutputStream ByteArrayOutputStream | |
68 StringReader ByteArrayInputStream) | |
69 (java.net URI URL MalformedURLException Socket))) | |
70 | |
71 | |
72 (def | |
73 ^{: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") | |
77 | |
78 (def | |
79 ^{:doc "Size, in bytes or characters, of the buffer used when | |
80 copying streams."} | |
81 *buffer-size* 1024) | |
82 | |
83 (def | |
84 ^{:doc "Type object for a Java primitive byte array."} | |
85 *byte-array-type* (class (make-array Byte/TYPE 0))) | |
86 | |
87 | |
88 (defn ^File file-str | |
89 "Concatenates args as strings and returns a java.io.File. Replaces | |
90 all / and \\ with File/separatorChar. Replaces ~ at the start of | |
91 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))) | |
100 | |
101 | |
102 (defmulti ^{:tag BufferedReader | |
103 :doc "Attempts to coerce its argument into an open | |
104 java.io.BufferedReader. Argument may be an instance of Reader, | |
105 BufferedReader, InputStream, File, URI, URL, Socket, or String. | |
106 | |
107 If argument is a String, it tries to resolve it first as a URI, then | |
108 as a local file name. URIs with a 'file' protocol are converted to | |
109 local file names. Uses *default-encoding* as the text encoding. | |
110 | |
111 Should be used inside with-open to ensure the Reader is properly | |
112 closed." | |
113 :arglists '([x])} | |
114 reader class) | |
115 | |
116 (defmethod reader Reader [x] | |
117 (BufferedReader. x)) | |
118 | |
119 (defmethod reader InputStream [^InputStream x] | |
120 (BufferedReader. (InputStreamReader. x *default-encoding*))) | |
121 | |
122 (defmethod reader File [^File x] | |
123 (reader (FileInputStream. x))) | |
124 | |
125 (defmethod reader URL [^URL x] | |
126 (reader (if (= "file" (.getProtocol x)) | |
127 (FileInputStream. (.getPath x)) | |
128 (.openStream x)))) | |
129 | |
130 (defmethod reader URI [^URI x] | |
131 (reader (.toURL x))) | |
132 | |
133 (defmethod reader String [^String x] | |
134 (try (let [url (URL. x)] | |
135 (reader url)) | |
136 (catch MalformedURLException e | |
137 (reader (File. x))))) | |
138 | |
139 (defmethod reader Socket [^Socket x] | |
140 (reader (.getInputStream x))) | |
141 | |
142 (defmethod reader :default [x] | |
143 (throw (Exception. (str "Cannot open " (pr-str x) " as a reader.")))) | |
144 | |
145 | |
146 (def | |
147 ^{: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) | |
151 | |
152 | |
153 (defmulti ^{:tag PrintWriter | |
154 :doc "Attempts to coerce its argument into an open java.io.PrintWriter | |
155 wrapped around a java.io.BufferedWriter. Argument may be an | |
156 instance of Writer, PrintWriter, BufferedWriter, OutputStream, File, | |
157 URI, URL, Socket, or String. | |
158 | |
159 If argument is a String, it tries to resolve it first as a URI, then | |
160 as a local file name. URIs with a 'file' protocol are converted to | |
161 local file names. | |
162 | |
163 Should be used inside with-open to ensure the Writer is properly | |
164 closed." | |
165 :arglists '([x])} | |
166 writer class) | |
167 | |
168 (defn- assert-not-appending [] | |
169 (when *append-to-writer* | |
170 (throw (Exception. "Cannot change an open stream to append mode.")))) | |
171 | |
172 (defmethod writer PrintWriter [x] | |
173 (assert-not-appending) | |
174 x) | |
175 | |
176 (defmethod writer BufferedWriter [^BufferedWriter x] | |
177 (assert-not-appending) | |
178 (PrintWriter. x)) | |
179 | |
180 (defmethod writer Writer [x] | |
181 (assert-not-appending) | |
182 ;; Writer includes sub-classes such as FileWriter | |
183 (PrintWriter. (BufferedWriter. x))) | |
184 | |
185 (defmethod writer OutputStream [^OutputStream x] | |
186 (assert-not-appending) | |
187 (PrintWriter. | |
188 (BufferedWriter. | |
189 (OutputStreamWriter. x *default-encoding*)))) | |
190 | |
191 (defmethod writer File [^File x] | |
192 (let [stream (FileOutputStream. x *append-to-writer*)] | |
193 (binding [*append-to-writer* false] | |
194 (writer stream)))) | |
195 | |
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 ">"))))) | |
200 | |
201 (defmethod writer URI [^URI x] | |
202 (writer (.toURL x))) | |
203 | |
204 (defmethod writer String [^String x] | |
205 (try (let [url (URL. x)] | |
206 (writer url)) | |
207 (catch MalformedURLException err | |
208 (writer (File. x))))) | |
209 | |
210 (defmethod writer Socket [^Socket x] | |
211 (writer (.getOutputStream x))) | |
212 | |
213 (defmethod writer :default [x] | |
214 (throw (Exception. (str "Cannot open <" (pr-str x) "> as a writer.")))) | |
215 | |
216 | |
217 (defn append-writer | |
218 "Like writer but opens file for appending. Does not work on streams | |
219 that are already open." | |
220 [x] | |
221 (binding [*append-to-writer* true] | |
222 (writer x))) | |
223 | |
224 | |
225 (defn write-lines | |
226 "Writes lines (a seq) to f, separated by newlines. f is opened with | |
227 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)))))) | |
235 | |
236 (defn read-lines | |
237 "Like clojure.core/line-seq but opens f with reader. Automatically | |
238 closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE." | |
239 [f] | |
240 (let [read-line (fn this [^BufferedReader rdr] | |
241 (lazy-seq | |
242 (if-let [line (.readLine rdr)] | |
243 (cons line (this rdr)) | |
244 (.close rdr))))] | |
245 (read-line (reader f)))) | |
246 | |
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)))))))) | |
257 | |
258 (defn spit | |
259 "Opposite of slurp. Opens f with writer, writes content, then | |
260 closes f." | |
261 [f content] | |
262 (with-open [^PrintWriter w (writer f)] | |
263 (.print w content))) | |
264 | |
265 (defn append-spit | |
266 "Like spit but appends to file." | |
267 [f content] | |
268 (with-open [^PrintWriter w (append-writer f)] | |
269 (.print w content))) | |
270 | |
271 (defn pwd | |
272 "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")) | |
276 | |
277 | |
278 | |
279 (defmacro with-out-writer | |
280 "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))) | |
286 | |
287 (defmacro with-out-append-writer | |
288 "Like with-out-writer but appends to file." | |
289 [f & body] | |
290 `(with-open [stream# (append-writer ~f)] | |
291 (binding [*out* stream#] | |
292 ~@body))) | |
293 | |
294 (defmacro with-in-reader | |
295 "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))) | |
300 | |
301 (defmulti | |
302 ^{: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. | |
305 | |
306 Does not close any streams except those it opens itself | |
307 (on a File). | |
308 | |
309 Writing a File fails if the parent directory does not exist." | |
310 :arglists '([input output])} | |
311 copy | |
312 (fn [input output] [(type input) (type output)])) | |
313 | |
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))))))) | |
321 | |
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)))))))) | |
330 | |
331 (defmethod copy [InputStream File] [^InputStream input ^File output] | |
332 (with-open [out (FileOutputStream. output)] | |
333 (copy input out))) | |
334 | |
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)))))))) | |
343 | |
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))))))) | |
351 | |
352 (defmethod copy [Reader File] [^Reader input ^File output] | |
353 (with-open [out (FileOutputStream. output)] | |
354 (copy input out))) | |
355 | |
356 (defmethod copy [File OutputStream] [^File input ^OutputStream output] | |
357 (with-open [in (FileInputStream. input)] | |
358 (copy in output))) | |
359 | |
360 (defmethod copy [File Writer] [^File input ^Writer output] | |
361 (with-open [in (FileInputStream. input)] | |
362 (copy in output))) | |
363 | |
364 (defmethod copy [File File] [^File input ^File output] | |
365 (with-open [in (FileInputStream. input) | |
366 out (FileOutputStream. output)] | |
367 (copy in out))) | |
368 | |
369 (defmethod copy [String OutputStream] [^String input ^OutputStream output] | |
370 (copy (StringReader. input) output)) | |
371 | |
372 (defmethod copy [String Writer] [^String input ^Writer output] | |
373 (copy (StringReader. input) output)) | |
374 | |
375 (defmethod copy [String File] [^String input ^File output] | |
376 (copy (StringReader. input) output)) | |
377 | |
378 (defmethod copy [*byte-array-type* OutputStream] [^"[B" input ^OutputStream output] | |
379 (copy (ByteArrayInputStream. input) output)) | |
380 | |
381 (defmethod copy [*byte-array-type* Writer] [^"[B" input ^Writer output] | |
382 (copy (ByteArrayInputStream. input) output)) | |
383 | |
384 (defmethod copy [*byte-array-type* File] [^"[B" input ^Writer output] | |
385 (copy (ByteArrayInputStream. input) output)) | |
386 | |
387 | |
388 (defn make-parents | |
389 "Creates all parent directories of file." | |
390 [^File file] | |
391 (.mkdirs (.getParentFile file))) | |
392 | |
393 (defmulti | |
394 ^{:doc "Converts argument into a Java byte array. Argument may be | |
395 a String, File, InputStream, or Reader. If the argument is already | |
396 a byte array, returns it." | |
397 :arglists '([arg])} | |
398 to-byte-array type) | |
399 | |
400 (defmethod to-byte-array *byte-array-type* [x] x) | |
401 | |
402 (defmethod to-byte-array String [^String x] | |
403 (.getBytes x *default-encoding*)) | |
404 | |
405 (defmethod to-byte-array File [^File x] | |
406 (with-open [input (FileInputStream. x) | |
407 buffer (ByteArrayOutputStream.)] | |
408 (copy input buffer) | |
409 (.toByteArray buffer))) | |
410 | |
411 (defmethod to-byte-array InputStream [^InputStream x] | |
412 (let [buffer (ByteArrayOutputStream.)] | |
413 (copy x buffer) | |
414 (.toByteArray buffer))) | |
415 | |
416 (defmethod to-byte-array Reader [^Reader x] | |
417 (.getBytes (slurp* x) *default-encoding*)) | |
418 |