code smell - More idiomatic line-by-line handling of a file in Clojure -
i'm trying read file (may or may not) have yaml frontmatter line-by-line using clojure, , return hashmap 2 vectors, 1 containing frontmatter lines , 1 containing else (i.e., body).
and example input file this:
--- key1: value1 key2: value2 --- body text paragraph 1 body text paragraph 2 body text paragraph 3
i have functioning code this, (admittedly inexperienced clojure) nose, reeks of code smell.
(defn process-file [f] (with-open [rdr (java.io.bufferedreader. (java.io.filereader. f))] (loop [lines (line-seq rdr) in-fm 0 frontmatter [] body []] (if-not (empty? lines) (let [line (string/trim (first lines))] (cond (zero? (count line)) (recur (rest lines) in-fm frontmatter body) (and (< in-fm 2) (= line "---")) (recur (rest lines) (inc in-fm) frontmatter body) (= in-fm 1) (recur (rest lines) in-fm (conj frontmatter line) body) :else (recur (rest lines) in-fm frontmatter (conj body line)))) (hash-map :frontmatter frontmatter :body body)))))
can point me more elegant way this? i'm going doing decent amount of line-by-line parsing in project, , i'd more idiomatic way of going if possible.
firstly, i'd put line-processing logic in own function called function reading in files. better yet, can make function dealing io take function map on lines argument, perhaps along these lines:
(require '[clojure.java.io :as io]) (defn process-file-with [f filename] (with-open [rdr (io/reader (io/file filename))] (f (line-seq rdr))))
note arrangement makes duty of f
realize of line seq needs before returns (because afterwards with-open
close underlying reader of line seq).
given division of responsibilities, line processing function might this, assuming first ---
must first non-blank line , blank lines skipped (as when using code question text):
(require '[clojure.string :as string]) (defn process-lines [lines] (let [ls (->> lines (map string/trim) (remove string/blank?))] (if (= (first ls) "---") (let [[front sep-and-body] (split-with #(not= "---" %) (next ls))] {:front (vec front) :body (vec (next sep-and-body))}) {:body (vec ls)})))
note calls vec
cause lines read in , returned in vector or pair of vectors (so can use process-lines
process-file-with
without reader being closed soon).
because reading lines actual file on disk decoupled processing seq of lines, can test latter part of process @ repl (and of course can made unit test):
;; input single string , split, of course (def test-lines ["---" "key1: value1" "key2: value2" "---" "" "body text paragraph 1" "" "body text paragraph 2" "" "body text paragraph 3"])
calling our function now:
user> (process-lines test-lines) {:front ("key1: value1" "key2: value2"), :body ("body text paragraph 1" "body text paragraph 2" "body text paragraph 3")}
Comments
Post a Comment