summaryrefslogtreecommitdiff
path: root/clojure/src/csvtool/core.clj
blob: 8654f3af7a1a8d6b5dbce95c24116d680378cfab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
(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")))))