Skip to content

Instantly share code, notes, and snippets.

@jafingerhut
Last active December 31, 2015 17:19
Show Gist options
  • Save jafingerhut/8019871 to your computer and use it in GitHub Desktop.
Save jafingerhut/8019871 to your computer and use it in GitHub Desktop.
str-seq-reader - Take a sequence of strings and return a Java Reader that, when read, returns the contents of the concatenation of those strings. Only as much of the sequence as needed by the reader is consumed. Examples of use at the bottom.
(ns piper.core
(:require [clojure.java.io :as io]
[clojure.string :as str])
(:import (java.io Reader Writer BufferedReader)))
(defn str-seq-reader
"Return a java.io.Reader that returns the same sequence of
characters as are in the concatenation of the sequence of strings
given as an argument. Consumes only as much of the sequence as needed
for any given read operation."
^Reader [str-seq]
(let [s (atom (seq str-seq))
n (atom 0)] ; # chars already consumed from first string in s
(proxy [Reader] []
(close
([]
(reset! s nil)))
(read
([^chars buf init-buf-off max-to-read]
(if-let [t @s]
(loop [t t
str-pos (int @n)
cur-buf-off (int init-buf-off)
max-to-read-left (int max-to-read)]
(let [cur-str (first t)
str-len (int (count cur-str))
str-left (int (- str-len str-pos))]
;; (binding [*out* *err*]
;; (println (format "str-pos=%3d max-left=%3d cur-str='%s'"
;; str-pos max-to-read-left cur-str)))
(if (pos? str-left)
(let [to-copy (min max-to-read-left str-left)]
(.getChars ^String cur-str (int str-pos)
(int (+ str-pos to-copy))
buf (int cur-buf-off))
(if (== to-copy max-to-read-left)
;; then we are done. Update state and return.
(do
(reset! s t)
(reset! n (+ str-pos to-copy))
;; return number of chars copied
(- (+ cur-buf-off to-copy) init-buf-off))
;; else we might have more to do. recur and check.
(recur t (int (+ str-pos to-copy))
(int (+ cur-buf-off to-copy))
(int (- max-to-read-left to-copy)))))
;; Else the cur string is exhausted. Go to next
;; string in sequence if there is one, without
;; copying anything this iteration.
(if-let [t (next t)]
(recur t
(int 0) ; start at beginning of next string
cur-buf-off
max-to-read-left)
;; else nothing left. Return number of
;; characters actually copied, or -1 if none.
(let [copied (- cur-buf-off init-buf-off)]
(reset! s nil)
(if (zero? copied)
-1
copied))))))
;; else nothing left
-1))))))
;; Examples of use: Read from file with name in-fname, transform each
;; input line with function f, and the resulting transformed contents
;; will be readable from Java Reader rdr2.
;; In this example, I am simply printing the contents of rdr2 to an
;; output file with name out-fname, but in general you could do
;; anything with the value of rdr2 that you would do with a Java
;; Reader.
(defn xform-file [in-fname f out-fname]
(with-open [wrtr (io/writer out-fname)]
(binding [*out* wrtr]
(with-open [rdr1 (io/reader in-fname)]
(with-open [rdr2 (BufferedReader.
(str-seq-reader (mapcat f (line-seq rdr1))))]
(doseq [ln (line-seq rdr2)]
(println ln)))))))
;; Examples:
;; Note that because of the mapcat call above, the function takes one
;; string as an argument, but it should return a sequence of strings
;; as its return value, or nil for no strings at all. Since line-seq
;; removes the line terminator character(s) from the ends of input
;; lines, you must provide them as part of the returned string
;; sequence, either by concatenating them, or as a separate element of
;; the sequence.
(defn prepend->
"Prepend each line with '> '"
[s]
(str "> " s "\n"))
(defn remove-blank
"Remove blank lines, leaving others unchanged."
[s]
(if (str/blank? s)
nil
[s "\n"]))
(defn remove-comment
"Remove lines beginning with a % character."
[^String s]
(if (.startsWith s "%")
nil
[s "\n"]))
;; (xform-file "in-file.txt" prepend-> "out-file.txt")
;; (xform-file "in-file.txt" remove-comment "out-file.txt")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment