2013-08-19 75 views
3

我試圖讀取一個文件(可以或不可以)YAML frontmatter逐行使用Clojure,並返回一個帶有兩個向量的hashmap包含前線和一個包含所有其他內容(即身體)。在Clojure中更習慣性地逐行處理文件

而例如輸入文件應該是這樣的:

--- 
key1: value1 
key2: value2 
--- 

Body text paragraph 1 

Body text paragraph 2 

Body text paragraph 3 

我已經運行的代碼,這樣做,但我(當然使用Clojure經驗不足)的鼻子,它惡臭碼味的。

(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))))) 

有人能指點我一個更優雅的方式來做到這一點嗎?我將在這個項目中進行大量的逐行解析,如果可能的話,我希望有一種更習慣的方式去解決它。

回答

6

首先,我將行處理邏輯放在它自己的函數中,以便從實際在文件中讀取的函數調用。更重要的是,你可以處理IO功能拍攝功能的行映射在作爲參數,也許是沿着這些線路:

(require '[clojure.java.io :as io]) 

(defn process-file-with [f filename] 
    (with-open [rdr (io/reader (io/file filename))] 
    (f (line-seq rdr)))) 

注意,這種安排使得它的f的責任,實現儘可能多的在它返回之前它所需的行seq(因爲之後with-open將關閉行seq的底層讀取器)。

考慮到責任分工,假定第一個---必須是第一個非空行,並且所有空行都被跳過(如同使用來自問題文本):

(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)}))) 

注意這會導致被讀取並在載體或者對向量(這樣我們就可以使用process-linesprocess-file-with未經讀者的返回所有行被太早關閉vec來電)。因爲從磁盤上的實際文件中讀取的行現在與處理一系列行不耦合,所以我們可以很容易地在REPL中測試這個過程的後一部分(當然這可以作爲一個單元測試):????????????

;; could input this as a single string and split, of course 
(def test-lines 
    ["---" 
    "key1: value1" 
    "key2: value2" 
    "---" 
    "" 
    "Body text paragraph 1" 
    "" 
    "Body text paragraph 2" 
    "" 
    "Body text paragraph 3"]) 

立即致電我們的函數:

user> (process-lines test-lines) 
{:front ("key1: value1" "key2: value2"), 
:body ("Body text paragraph 1" 
     "Body text paragraph 2" 
     "Body text paragraph 3")} 
+0

這太棒了。我知道我只是在錯誤地看問題。謝謝! –

0

實際上,慣用的方式來使用Clojure的是避免返回「一個HashMap有兩個載體」,並把這個文件看作是(懶惰)做線序

然後,將處理線的順序的功能決定文件是否有YAML frontmatter或不

是這樣的:

(use '[clojure.java.io :only (reader)]) 
(let [s (line-seq (reader "YOURFILENAMEHERE"))] 
    (if (= "---\n" (take 1 (line-seq (reader "YOURFILENAMEHERE")))) 
    (process-seq-with-frontmatter s) 
    (process-seq-without-frontmatter s)) 

順便說一下,這是一個戒菸和骯髒的解決方案;兩件事情,以改善:

  1. 通知我創建了兩個seqs同一個文件,這將是最好創建只有一個,使第一線的檢查,這樣就不會在穿過第一要素的seq(像一個偷看,而不是一個流行)
  2. 我認爲這將是一個多方法'process-seq'(當然有一個更好的名稱),將根據第一行seq
相關問題