diff options
author | Stuart Sierra <mail@stuartsierra.com> | 2008-12-19 16:48:11 +0000 |
---|---|---|
committer | Stuart Sierra <mail@stuartsierra.com> | 2008-12-19 16:48:11 +0000 |
commit | cf2be204e8fc112d94716e966b92b5e02d596686 (patch) | |
tree | 163bdaa8479cdf038cfb35012b1efb0d387e3af4 /src | |
parent | 95b075047e73e4ff8a3c484df74970fabb563a8a (diff) |
duck_streams.clj: rewrote w/ multimethods, added slurp*, file, & read-lines
Diffstat (limited to 'src')
-rw-r--r-- | src/clojure/contrib/duck_streams.clj | 210 |
1 files changed, 136 insertions, 74 deletions
diff --git a/src/clojure/contrib/duck_streams.clj b/src/clojure/contrib/duck_streams.clj index d77c8b83..27964262 100644 --- a/src/clojure/contrib/duck_streams.clj +++ b/src/clojure/contrib/duck_streams.clj @@ -1,7 +1,7 @@ ;;; duck_streams.clj -- duck-typed I/O streams for Clojure -;; by Stuart Sierra <mail@stuartsierra.com> -;; April 8, 2008 +;; by Stuart Sierra, http://stuartsierra.com/ +;; December 19, 2008 ;; Copyright (c) Stuart Sierra, 2008. All rights reserved. The use ;; and distribution terms for this software are covered by the Eclipse @@ -16,29 +16,37 @@ ;; The 'reader' and 'writer' functions will open and return an ;; instance of java.io.BufferedReader and java.io.PrintWriter, ;; respectively, for a variety of argument types -- filenames as -;; strings, URLs, java.io.File's, etc. These functions are not very -;; efficient, because they have to perform a number of 'instance?' -;; checks, but they are convenient when you just want to open a file -;; and don't want to deal with all the Java I/O classes. +;; strings, URLs, java.io.File's, etc. 'reader' even works on http +;; URLs. ;; -;; This file also defines two convenience functions, 'spit' (opposite -;; of 'slurp') and 'write-lines' (opposite of 'line-seq'). +;; Note: this is not really "duck typing" as implemented in languages +;; like Ruby. A better name would have been "do-what-I-mean-streams" +;; or "just-give-me-a-stream", but ducks are funnier. + + +;; CHANGE LOG +;; +;; December 19, 2008: rewrote reader and writer as multimethods; added +;; slurp*, file, and read-lines +;; +;; April 8, 2008: first version + (ns clojure.contrib.duck-streams - (:import - (java.io Reader InputStream InputStreamReader FileReader - BufferedReader File PrintWriter OutputStream - OutputStreamWriter BufferedWriter Writer FileWriter) - (java.net URI URL MalformedURLException))) - -(defmacro bufr - {:private true} - [reader] + (:import + (java.io Reader InputStream InputStreamReader FileReader + BufferedReader File PrintWriter OutputStream + OutputStreamWriter BufferedWriter Writer FileWriter) + (java.net URI URL MalformedURLException))) + + + +(defmacro #^{:private true} bufr [reader] `(new java.io.BufferedReader ~reader)) -(defn reader - "Attempts to coerce its argument into an open +(defmulti #^{:tag BufferedReader + :doc "Attempts to coerce its argument into an open java.io.BufferedReader. Argument may be an instance of Reader, BufferedReader, InputStream, File, URI, URL, or String. @@ -47,31 +55,40 @@ local file names. Should be used inside with-open to ensure the Reader is properly - closed." - [x] - (cond - (instance? BufferedReader x) x - (instance? Reader x) (bufr x) - (instance? InputStream x) (bufr (new InputStreamReader x)) - (instance? File x) (bufr (new FileReader #^File x)) - (instance? URL x) (if (= (. #^URL x (getProtocol)) "file") - (bufr (new FileReader (. #^URL x (getPath)))) - (bufr (new InputStreamReader (. #^URL x (openStream))))) - (instance? URI x) (reader (. #^URI x (toURL))) - (instance? String x) (try (let [url (new URL x)] - (reader url)) - (catch MalformedURLException err - (bufr (new FileReader #^String x)))) - :else (throw (new Exception (str "Cannot coerce " (class x) - " into a Reader."))))) - -(defmacro bufw - {:private true} - [writer] + closed."} + reader class) + +(defmethod reader BufferedReader [x] x) + +(defmethod reader BufferedReader [x] (bufr x)) + +(defmethod reader InputStream [x] (bufr (InputStreamReader. x))) + +(defmethod reader File [#^File x] (bufr (FileReader. x))) + +(defmethod reader URL [#^URL x] + (if (= "file" (.getProtocol x)) + (bufr (FileReader. (.getPath x))) + (bufr (InputStreamReader. (.openStream x))))) + +(defmethod reader URI [#^URI x] (reader (.toURL x))) + +(defmethod reader String [#^String x] + (try (let [url (URL. x)] + (reader url)) + (catch MalformedURLException e + (bufr (FileReader. #^File (file x)))))) + +(defmethod reader :default [x] + (throw (Exception. (str "Cannot open <" (pr-str x) "> as a reader.")))) + + + +(defmacro #^{:private true} bufw [writer] `(new java.io.PrintWriter (new java.io.BufferedWriter ~writer))) -(defn writer - "Attempts to coerce its argument into an open java.io.PrintWriter +(defmulti #^{:tag PrintWriter + :doc "Attempts to coerce its argument into an open java.io.PrintWriter wrapped around a java.io.BufferedWriter. Argument may be an instance of Writer, PrintWriter, BufferedWriter, OutputStream, File, URI, URL, or String. @@ -81,39 +98,84 @@ local file names. Should be used inside with-open to ensure the Writer is properly - closed." - [x] - (cond - (instance? PrintWriter x) x - (instance? BufferedWriter x) (new PrintWriter #^BufferedWriter x) - (instance? Writer x) (bufw x) ; includes FileWriter - (instance? OutputStream x) (bufw (new OutputStreamWriter x)) - (instance? File x) (bufw (new FileWriter #^File x)) - (instance? URL x) (if (= (. #^URL x (getProtocol)) "file") - (bufw (new FileWriter (. #^URL x (getPath)))) - (throw (new Exception (str "Cannot write to non-file URL <" x ">.")))) - (instance? URI x) (writer (. #^URI x (toURL))) - (instance? String x) (try (let [url (new URL x)] - (writer url)) - (catch MalformedURLException err - (bufw (new FileWriter #^String x)))) - :else (throw (new Exception (str "Cannot coerce " (class x) - " into a Writer."))))) + closed."} + writer class) + +(defmethod writer PrintWriter [x] x) + +(defmethod writer BufferedWriter [#^BufferedWriter x] (PrintWriter. x)) + +(defmethod writer Writer [x] (bufw x)) ; includes FileWriter + +(defmethod writer File [#^File x] (bufw (FileWriter. x))) + +(defmethod writer URL [#^URL x] + (if (= "file" (.getProtocol x)) + (bufw (FileWriter. (.getPath x))) + (throw (Exception. (str "Cannot write to non-file URL <" x ">"))))) + +(defmethod writer URI [#^URI x] (writer (.toURL x))) + +(defmethod writer String [#^String x] + (try (let [url (URL. x)] + (writer url)) + (catch MalformedURLException err + (bufw (FileWriter. #^File (file x)))))) + +(defmethod writer :default [x] + (throw (Exception. (str "Cannot open <" (pr-str x) "> as a writer.")))) + + (defn write-lines - "Opposite of 'line-seq'. Writes lines (a seq) to writer (an open - java.io.PrintWriter), separated by newlines." - [#^PrintWriter writer lines] - (let [line (first lines)] - (when line - (. writer (write (str line))) - (. writer (println)) - (recur writer (rest lines))))) + "Writes lines (a seq) to f, separated by newlines. f is opened with + writer." + [f lines] + (with-open [#^PrintWriter writer (writer f)] + (loop [lines lines] + (when-let [line (first lines)] + (.write writer (str line)) + (.println writer) + (recur (rest lines)))))) + +(defn read-lines + "Like clojure.core/line-seq but opens f with reader. Automatically + closes the reader AFTER YOU CONSUME THE ENTIRE SEQUENCE." + [f] + (let [read-line (fn this [#^BufferedReader rdr] + (if-let [line (.readLine rdr)] + (lazy-cons line (this rdr)) + (.close rdr)))] + (read-line (reader f)))) + +(defn slurp* + "Like clojure.core/slurp but opens f with reader." + [f] + (with-open [#^BufferedReader r (reader f)] + (let [sb (StringBuilder.)] + (loop [c (.read r)] + (if (neg? c) + (str sb) + (do (.append sb (char c)) + (recur (.read r)))))))) (defn spit - "Opposite of 'slurp'. Writes 'contents' to the file named by - 'filename'." - [filename contents] - (with-open [w (#^PrintWriter writer filename)] - (. w (print contents)))) - + "Opposite of slurp. Opens f with writer, writes content, then + closes f." + [f content] + (with-open [#^PrintWriter w (writer f)] + (.print w content))) + +(defn #^File file + "Concatenates args as strings returns a java.io.File. Replaces all + / and \\ with File/separatorChar. Replaces ~ at the start of the + path with the user.home system property." + [& args] + (let [#^String s (apply str args) + s (.replace s \/ File/separatorChar) + s (.replace s \\ File/separatorChar) + s (if (.startsWith s "~") + (str (System/getProperty "user.home") + File/separatorChar (subs s 1)) + s)] + (File. s))) |