(ns csvtool.core (:require [clojure.data.csv :as csv] [clojure.pprint :refer [pprint]] [clojure.tools.cli :refer [parse-opts]] [clojure.java.io :as io]) (:gen-class)) (defn mk-mapping [row & keys] (->> (zipmap keys row) (#(dissoc % :ignore)))) (defn my-read [] (with-open [reader (io/reader "in-file.csv")] (doall (map handle-record (csv/read-csv reader))))) (defn my-write [] (with-open [writer (io/writer "out-file.csv")] (csv/write-csv writer [["abc" "def"] ["ghi" "jkl"]]))) (defmacro wrap-matcher ([] identity) ([body] `(fn [~'record] (let [~'get #(get ~'record %) ~'set #(assoc ~'record %1 %2)] ~body))) ([expr & next] `(fn [record#] ((wrap-matcher ~@next) ((wrap-matcher ~expr) record#))))) (defmacro matches [keyword pattern] `(wrap-matcher (let [v# (~'get ~keyword)] (and v# (re-matches ~pattern (~'get ~keyword)))))) (defmacro defchain ([] identity) ([[pred & arg]] (let [expr2 (if (= :do (first arg)) `((wrap-matcher ~@(rest arg))) arg)] `(fn [record#] (if (~pred record#) (~@expr2 record#) record#)))) ([x & next] `(fn [record#] ((defchain ~@next) ((defchain ~x) record#))))) (def always (constantly true)) (def other-rules (defchain [(matches :desc #"Amazon.com.*") :do (set :desc "Amazon purchase")])) (def my-rules (defchain [always :do (set :account2 "expenses:unknown")] [(matches :desc #"(?i)key food.*") :do (set :desc "Key Foods") (set :account2 "expenses:groceries")] [(matches :desc #"(?i)google \*cloud") :do (set :desc "Google Cloud") (set :account2 "expenses:services")] [always other-rules])) (defn handle-record [row] (let [record (mk-mapping row :date :postdate :desc :category :type :amount :memo)] (my-rules record) )) (defn process-file [path] (with-open [reader (io/reader path)] (doall (let [csv (csv/read-csv reader) ;; Skip header data (drop 1 csv) sample (take 10 data) records (map handle-record sample)] (pprint records))))) (def cli-options [["-f" "--file" :required "Input file"] ["-h" "--help"]]) (defn -main [& args] (pprint (parse-opts args cli-options)) (let [{:keys [options arguments errors summary]} (parse-opts args cli-options)] (cond (:file options) (process-file (:file options)) :else (do (println "Failed")))))