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

Popular posts from this blog

Detect support for Shoutcast ICY MP3 without navigator.userAgent in Firefox? -

web - SVG not rendering properly in Firefox -

java - JavaFX 2 slider labelFormatter not being used -