(ns parse (:require [clojure.spec.alpha :as s] [clojure.walk :as walk])) (s/def ::token (s/+ (set "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-:"))) (s/def ::number (s/+ (set "01234567890-."))) (s/def ::quoted-token (s/cat :_ #{\"} :token (s/+ (set "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-:./% ()")) :_ #{\"})) (s/def ::whitespace (s/+ #{\space})) (s/def ::meta (s/cat :_ ::whitespace :key ::token :_ #{\:} :_ ::whitespace :value ::quoted-token :_ #{\newline})) (s/def ::posting (s/cat :_ ::whitespace :account ::token :_ ::whitespace :amount ::number :_ ::whitespace :currency ::token :_ #{\newline} :meta (s/* ::meta))) (s/def ::transaction (s/cat :date ::token :_ ::whitespace :_ ::token :_ ::whitespace :payee (s/? ::quoted-token) :_ (s/? ::whitespace) :desc ::quoted-token :_ #{\newline} :meta (s/* ::meta) :postings (s/* ::posting) :_ (s/* #{\newline}))) (s/def ::transactions (s/+ ::transaction)) (defn kv->map "Convert vector [{:key \"x\" :value \"y\"}] to {:x y}" [vec] (into {} (for [{:keys [key value]} vec] [(keyword key) value]))) (defn convert-parse-tree "Reformat the parse tree into a similar data structure as used during import." [tree] (walk/postwalk #(cond ;; vector of chars to string (and (vector? %) (= (type (first %)) java.lang.Character)) (apply str %) ;; posting amount to bigdec (and (= (type %) clojure.lang.PersistentArrayMap) (contains? % :amount)) (dissoc (update % :amount bigdec) :_) ;; flatten quoted-tokens (and (= (type %) clojure.lang.PersistentArrayMap) (contains? % :token)) (:token %) ;; drop :_ keys (= (type %) clojure.lang.PersistentArrayMap) (dissoc % :_) ;; convert vector of :key/:value maps to a map (and (vector? %) (= (type (first %)) clojure.lang.PersistentArrayMap) (contains? (first %) :key)) (kv->map %) :else %) tree)) (defn parse "Parse a Beancount transaction. Use so we can convert the data generated during import with the hand-generated Beancount transactions." [text] (let [tree (s/conform ::transactions (conj (vec text) \newline))] (convert-parse-tree tree)))