(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 record-action [expr & rest] (let [actions {:set (fn [keyword value & rest] (fn [record] [(assoc record keyword value) rest])) :mod (fn [keyword modf & rest] (fn [record] [(update record keyword modf) rest]))}] (if (fn? expr) (fn [record] [(expr record) rest]) (apply (get actions expr) rest)))) (defn record-actions [& exprs] (fn [record] (let [[next-record rest] ((apply record-action exprs) record) next-action (if (empty? rest) identity (apply record-actions rest))] (next-action next-record)))) (defn record-test [keyword pred] (fn [record] (if (contains? record keyword) (pred (get record keyword)) nil))) (defmacro mk-rule [test & actions] `(fn [record#] (if (~test record#) ((~record-actions ~@actions) record#) record#))) (defmacro mk-chain ([] identity) ([rule] `(mk-rule ~@rule)) ([rule & next] `(fn [record#] ((mk-chain ~@next) ((mk-chain ~rule) record#))))) (defmacro defchain [name & rules] `(def ~name (mk-chain ~@rules))) (def always (constantly true)) (defn matches [keyword pattern] (fn [record] (and (contains? record keyword) (re-matches pattern (get record keyword))))) (defn matches-any [keyword & patterns] (fn [record] (let [v (get record keyword)] (some (fn [pattern] (re-matches pattern v)) patterns)))) (defchain dining [(matches-any :desc #"(?i)chipotle.*" #"(?i)tappo- location.*") :set :account2 "expenses:dining"]) (defchain default [always :set :account2 "expenses:unknown"] [always dining] [(matches :desc #"(?i)key food.*") :set :desc "Key Foods" :set :account2 "expenses:groceries"] [(matches :desc #"(?i)google \*cloud") :set :account2 "expenses:services"]) (defn handle-record [row] (let [record (mk-mapping row :date :postdate :desc :category :type :amount :memo)] (default 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")))))