Mercurial > lasercutter
comparison 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 |
comparison
equal
deleted
inserted
replaced
9:35cf337adfcf | 10:ef7dbbd6452c |
---|---|
1 ;;; io.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.BufferedWriter, | |
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: Most functions here are deprecated. Use | |
30 ;; clojure.java.io | |
31 ;; | |
32 ;; May 13, 2009: added functions to open writers for appending | |
33 ;; | |
34 ;; May 3, 2009: renamed file to file-str, for compatibility with | |
35 ;; clojure.contrib.java. reader/writer no longer use this | |
36 ;; function. | |
37 ;; | |
38 ;; February 16, 2009: (lazy branch) fixed read-lines to work with lazy | |
39 ;; Clojure. | |
40 ;; | |
41 ;; January 10, 2009: added *default-encoding*, so streams are always | |
42 ;; opened as UTF-8. | |
43 ;; | |
44 ;; December 19, 2008: rewrote reader and writer as multimethods; added | |
45 ;; slurp*, file, and read-lines | |
46 ;; | |
47 ;; April 8, 2008: first version | |
48 | |
49 | |
50 | |
51 (ns | |
52 ^{:author "Stuart Sierra", | |
53 :doc "This file defines polymorphic I/O utility functions for Clojure. | |
54 | |
55 The Streams protocol defines reader, writer, input-stream and | |
56 output-stream methods that return BufferedReader, BufferedWriter, | |
57 BufferedInputStream and BufferedOutputStream instances (respectively), | |
58 with default implementations extended to a variety of argument | |
59 types: URLs or filenames as strings, java.io.File's, Sockets, etc."} | |
60 clojure.contrib.io | |
61 (:refer-clojure :exclude (spit)) | |
62 (:import | |
63 (java.io Reader InputStream InputStreamReader PushbackReader | |
64 BufferedReader File OutputStream | |
65 OutputStreamWriter BufferedWriter Writer | |
66 FileInputStream FileOutputStream ByteArrayOutputStream | |
67 StringReader ByteArrayInputStream | |
68 BufferedInputStream BufferedOutputStream | |
69 CharArrayReader) | |
70 (java.net URI URL MalformedURLException Socket))) | |
71 | |
72 | |
73 (def | |
74 ^{: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") | |
78 | |
79 (def | |
80 ^{:doc "Size, in bytes or characters, of the buffer used when | |
81 copying streams."} | |
82 *buffer-size* 1024) | |
83 | |
84 (def | |
85 ^{:doc "Type object for a Java primitive byte array."} | |
86 *byte-array-type* (class (make-array Byte/TYPE 0))) | |
87 | |
88 (def | |
89 ^{:doc "Type object for a Java primitive char array."} | |
90 *char-array-type* (class (make-array Character/TYPE 0))) | |
91 | |
92 | |
93 (defn ^File file-str | |
94 "Concatenates args as strings and returns a java.io.File. Replaces | |
95 all / and \\ with File/separatorChar. Replaces ~ at the start of | |
96 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))) | |
106 | |
107 (def | |
108 ^{: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) | |
113 | |
114 (defn- assert-not-appending [] | |
115 (when *append* | |
116 (throw (Exception. "Cannot change an open stream to append mode.")))) | |
117 | |
118 ;; @todo -- Both simple and elaborate methods for controlling buffering of | |
119 ;; in the Streams protocol were implemented, considered, and postponed | |
120 ;; see http://groups.google.com/group/clojure-dev/browse_frm/thread/3e39e9b3982f542b | |
121 (defprotocol Streams | |
122 (reader [x] | |
123 "Attempts to coerce its argument into an open java.io.Reader. | |
124 The default implementations of this protocol always return a | |
125 java.io.BufferedReader. | |
126 | |
127 Default implementations are provided for Reader, BufferedReader, | |
128 InputStream, File, URI, URL, Socket, byte arrays, character arrays, | |
129 and String. | |
130 | |
131 If argument is a String, it tries to resolve it first as a URI, then | |
132 as a local file name. URIs with a 'file' protocol are converted to | |
133 local file names. If this fails, a final attempt is made to resolve | |
134 the string as a resource on the CLASSPATH. | |
135 | |
136 Uses *default-encoding* as the text encoding. | |
137 | |
138 Should be used inside with-open to ensure the Reader is properly | |
139 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 a | |
143 java.io.BufferedWriter. | |
144 | |
145 Default implementations are provided for Writer, BufferedWriter, | |
146 OutputStream, File, URI, URL, Socket, and String. | |
147 | |
148 If the argument is a String, it tries to resolve it first as a URI, then | |
149 as a local file name. URIs with a 'file' protocol are converted to | |
150 local file names. | |
151 | |
152 Should be used inside with-open to ensure the Writer is properly | |
153 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 a | |
157 java.io.BufferedInputStream. | |
158 | |
159 Default implementations are defined for OutputStream, File, URI, URL, | |
160 Socket, byte array, and String arguments. | |
161 | |
162 If the argument is a String, it tries to resolve it first as a URI, then | |
163 as a local file name. URIs with a 'file' protocol are converted to | |
164 local file names. | |
165 | |
166 Should be used inside with-open to ensure the InputStream is properly | |
167 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 a | |
171 java.io.BufferedOutputStream. | |
172 | |
173 Default implementations are defined for OutputStream, File, URI, URL, | |
174 Socket, and String arguments. | |
175 | |
176 If the argument is a String, it tries to resolve it first as a URI, then | |
177 as a local file name. URIs with a 'file' protocol are converted to | |
178 local file names. | |
179 | |
180 Should be used inside with-open to ensure the OutputStream is | |
181 properly closed.")) | |
182 | |
183 (def default-streams-impl | |
184 {: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.")))}) | |
188 | |
189 (extend File | |
190 Streams | |
191 (assoc default-streams-impl | |
192 :input-stream #(input-stream (FileInputStream. ^File %)) | |
193 :output-stream #(let [stream (FileOutputStream. ^File % *append*)] | |
194 (binding [*append* false] | |
195 (output-stream stream))))) | |
196 (extend URL | |
197 Streams | |
198 (assoc default-streams-impl | |
199 :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 URI | |
208 Streams | |
209 (assoc default-streams-impl | |
210 :input-stream #(input-stream (.toURL ^URI %)) | |
211 :output-stream #(output-stream (.toURL ^URI %)))) | |
212 (extend String | |
213 Streams | |
214 (assoc default-streams-impl | |
215 :input-stream #(try | |
216 (input-stream (URL. %)) | |
217 (catch MalformedURLException e | |
218 (input-stream (File. ^String %)))) | |
219 :output-stream #(try | |
220 (output-stream (URL. %)) | |
221 (catch MalformedURLException err | |
222 (output-stream (File. ^String %)))))) | |
223 (extend Socket | |
224 Streams | |
225 (assoc default-streams-impl | |
226 :input-stream #(.getInputStream ^Socket %) | |
227 :output-stream #(output-stream (.getOutputStream ^Socket %)))) | |
228 (extend *byte-array-type* | |
229 Streams | |
230 (assoc default-streams-impl :input-stream #(input-stream (ByteArrayInputStream. %)))) | |
231 (extend *char-array-type* | |
232 Streams | |
233 (assoc default-streams-impl :reader #(reader (CharArrayReader. %)))) | |
234 (extend Object | |
235 Streams | |
236 default-streams-impl) | |
237 | |
238 (extend Reader | |
239 Streams | |
240 (assoc default-streams-impl :reader #(BufferedReader. %))) | |
241 (extend BufferedReader | |
242 Streams | |
243 (assoc default-streams-impl :reader identity)) | |
244 (defn- inputstream->reader | |
245 [^InputStream is] | |
246 (reader (InputStreamReader. is *default-encoding*))) | |
247 (extend InputStream | |
248 Streams | |
249 (assoc default-streams-impl :input-stream #(BufferedInputStream. %) | |
250 :reader inputstream->reader)) | |
251 (extend BufferedInputStream | |
252 Streams | |
253 (assoc default-streams-impl | |
254 :input-stream identity | |
255 :reader inputstream->reader)) | |
256 | |
257 (extend Writer | |
258 Streams | |
259 (assoc default-streams-impl :writer #(do (assert-not-appending) | |
260 (BufferedWriter. %)))) | |
261 (extend BufferedWriter | |
262 Streams | |
263 (assoc default-streams-impl :writer #(do (assert-not-appending) %))) | |
264 (defn- outputstream->writer | |
265 [^OutputStream os] | |
266 (assert-not-appending) | |
267 (writer (OutputStreamWriter. os *default-encoding*))) | |
268 (extend OutputStream | |
269 Streams | |
270 (assoc default-streams-impl | |
271 :output-stream #(do (assert-not-appending) | |
272 (BufferedOutputStream. %)) | |
273 :writer outputstream->writer)) | |
274 (extend BufferedOutputStream | |
275 Streams | |
276 (assoc default-streams-impl | |
277 :output-stream #(do (assert-not-appending) %) | |
278 :writer outputstream->writer)) | |
279 | |
280 (defn append-output-stream | |
281 "Like output-stream but opens file for appending. Does not work on streams | |
282 that are already open." | |
283 {:deprecated "1.2"} | |
284 [x] | |
285 (binding [*append* true] | |
286 (output-stream x))) | |
287 | |
288 (defn append-writer | |
289 "Like writer but opens file for appending. Does not work on streams | |
290 that are already open." | |
291 {:deprecated "1.2"} | |
292 [x] | |
293 (binding [*append* true] | |
294 (writer x))) | |
295 | |
296 (defn write-lines | |
297 "Writes lines (a seq) to f, separated by newlines. f is opened with | |
298 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)))))) | |
306 | |
307 (defn read-lines | |
308 "Like clojure.core/line-seq but opens f with reader. Automatically | |
309 closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE." | |
310 [f] | |
311 (let [read-line (fn this [^BufferedReader rdr] | |
312 (lazy-seq | |
313 (if-let [line (.readLine rdr)] | |
314 (cons line (this rdr)) | |
315 (.close rdr))))] | |
316 (read-line (reader f)))) | |
317 | |
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)))))))) | |
329 | |
330 (defn spit | |
331 "Opposite of slurp. Opens f with writer, writes content, then | |
332 closes f." | |
333 {:deprecated "1.2"} | |
334 [f content] | |
335 (with-open [^Writer w (writer f)] | |
336 (.write w content))) | |
337 | |
338 (defn append-spit | |
339 "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))) | |
344 | |
345 (defn pwd | |
346 "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")) | |
351 | |
352 (defmacro with-out-writer | |
353 "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))) | |
359 | |
360 (defmacro with-out-append-writer | |
361 "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))) | |
367 | |
368 (defmacro with-in-reader | |
369 "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))) | |
374 | |
375 (defmulti | |
376 ^{: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. | |
380 | |
381 Does not close any streams except those it opens itself | |
382 (on a File). | |
383 | |
384 Writing a File fails if the parent directory does not exist." | |
385 :arglists '([input output])} | |
386 copy | |
387 (fn [input output] [(type input) (type output)])) | |
388 | |
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))))))) | |
396 | |
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)))))))) | |
405 | |
406 (defmethod copy [InputStream File] [^InputStream input ^File output] | |
407 (with-open [out (FileOutputStream. output)] | |
408 (copy input out))) | |
409 | |
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)))))))) | |
418 | |
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))))))) | |
426 | |
427 (defmethod copy [Reader File] [^Reader input ^File output] | |
428 (with-open [out (FileOutputStream. output)] | |
429 (copy input out))) | |
430 | |
431 (defmethod copy [File OutputStream] [^File input ^OutputStream output] | |
432 (with-open [in (FileInputStream. input)] | |
433 (copy in output))) | |
434 | |
435 (defmethod copy [File Writer] [^File input ^Writer output] | |
436 (with-open [in (FileInputStream. input)] | |
437 (copy in output))) | |
438 | |
439 (defmethod copy [File File] [^File input ^File output] | |
440 (with-open [in (FileInputStream. input) | |
441 out (FileOutputStream. output)] | |
442 (copy in out))) | |
443 | |
444 (defmethod copy [String OutputStream] [^String input ^OutputStream output] | |
445 (copy (StringReader. input) output)) | |
446 | |
447 (defmethod copy [String Writer] [^String input ^Writer output] | |
448 (copy (StringReader. input) output)) | |
449 | |
450 (defmethod copy [String File] [^String input ^File output] | |
451 (copy (StringReader. input) output)) | |
452 | |
453 (defmethod copy [*char-array-type* OutputStream] [input ^OutputStream output] | |
454 (copy (CharArrayReader. input) output)) | |
455 | |
456 (defmethod copy [*char-array-type* Writer] [input ^Writer output] | |
457 (copy (CharArrayReader. input) output)) | |
458 | |
459 (defmethod copy [*char-array-type* File] [input ^File output] | |
460 (copy (CharArrayReader. input) output)) | |
461 | |
462 (defmethod copy [*byte-array-type* OutputStream] [^"[B" input ^OutputStream output] | |
463 (copy (ByteArrayInputStream. input) output)) | |
464 | |
465 (defmethod copy [*byte-array-type* Writer] [^"[B" input ^Writer output] | |
466 (copy (ByteArrayInputStream. input) output)) | |
467 | |
468 (defmethod copy [*byte-array-type* File] [^"[B" input ^Writer output] | |
469 (copy (ByteArrayInputStream. input) output)) | |
470 | |
471 (defn make-parents | |
472 "Creates all parent directories of file." | |
473 [^File file] | |
474 (.mkdirs (.getParentFile file))) | |
475 | |
476 (defmulti | |
477 ^{:doc "Converts argument into a Java byte array. Argument may be | |
478 a String, File, InputStream, or Reader. If the argument is already | |
479 a byte array, returns it." | |
480 :arglists '([arg])} | |
481 to-byte-array type) | |
482 | |
483 (defmethod to-byte-array *byte-array-type* [x] x) | |
484 | |
485 (defmethod to-byte-array String [^String x] | |
486 (.getBytes x *default-encoding*)) | |
487 | |
488 (defmethod to-byte-array File [^File x] | |
489 (with-open [input (FileInputStream. x) | |
490 buffer (ByteArrayOutputStream.)] | |
491 (copy input buffer) | |
492 (.toByteArray buffer))) | |
493 | |
494 (defmethod to-byte-array InputStream [^InputStream x] | |
495 (let [buffer (ByteArrayOutputStream.)] | |
496 (copy x buffer) | |
497 (.toByteArray buffer))) | |
498 | |
499 (defmethod to-byte-array Reader [^Reader x] | |
500 (.getBytes (slurp* x) *default-encoding*)) | |
501 | |
502 (defmulti relative-path-string | |
503 "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) | |
507 | |
508 (defmethod relative-path-string String [^String s] | |
509 (relative-path-string (File. s))) | |
510 | |
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))) | |
515 | |
516 (defmulti ^File as-file | |
517 "Interpret a String or a java.io.File as a File. Building block | |
518 for clojure.contrib.java/file, which you should prefer | |
519 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) | |
524 | |
525 (defn ^File file | |
526 "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))) | |
534 | |
535 (defn delete-file | |
536 "Delete file f. Raise an exception if it fails unless silently is true." | |
537 [f & [silently]] | |
538 (or (.delete (file f)) | |
539 silently | |
540 (throw (java.io.IOException. (str "Couldn't delete " f))))) | |
541 | |
542 (defn delete-file-recursively | |
543 "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))) | |
551 | |
552 (defmulti | |
553 ^{:deprecated "1.2" | |
554 :doc "Coerces argument (URL, URI, or String) to a java.net.URL." | |
555 :arglists '([arg])} | |
556 as-url type) | |
557 | |
558 (defmethod as-url URL [x] x) | |
559 | |
560 (defmethod as-url URI [^URI x] (.toURL x)) | |
561 | |
562 (defmethod as-url String [^String x] (URL. x)) | |
563 | |
564 (defmethod as-url File [^File x] (.toURL x)) |