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
|