-
Notifications
You must be signed in to change notification settings - Fork 6
/
diff.cljc
157 lines (132 loc) · 4.85 KB
/
diff.cljc
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
(ns fulcro-spec.diff
(:require
[clojure.set :as set]
[clojure.walk :as walk]))
(declare diff)
(def nf '..nothing..)
(defn diff-elem? [?de]
(and (vector? ?de)
(= 4 (count ?de))
(let [[p _ m _] ?de]
(and (= p :+) (= m :-)))))
(defn diff-elem
([] [])
([exp got]
(assert (not (diff-elem? exp)))
(assert (not (diff-elem? got)))
[:+ exp :- got]))
(defn extract [?d]
(assert (and (vector? ?d) (= 2 (count ?d))))
(assert (vector? (first ?d)))
(assert (diff-elem? (second ?d)))
(let [[path [_ exp _ got]] ?d]
{:path path :exp exp :got got}))
(defn diff? [?d]
(and ?d (map? ?d)
(every? vector (keys ?d))
(every? diff-elem? (vals ?d))))
(defn map-keys [f m] (into {} (for [[k v] m] [(f k) v])))
(defn- map-diff [ks exp act]
(loop [[k & ks] ks, exp exp, act act, path [], diffs {}]
(if (and (empty? ks) (nil? k)) diffs
(let [ev (get exp k nf)
av (get act k nf)]
(if (and ev av (= ev av))
(recur ks exp act path diffs)
(let [d (diff ev av :recur)
diffs (cond
(diff-elem? d) (assoc diffs [k] d)
(diff? d) (merge diffs (map-keys #(vec (cons k %)) d))
(empty? d) diffs
:else (throw (ex-info "This should not have happened"
{:d d :exp exp :act act})))]
(recur ks exp act [] diffs)))))))
(defn- seq-diff [exp act]
(let [exp-count (count exp)
act-count (count act)]
(loop [[i & is] (range), [e & es :as exp] exp, [a & as :as act] act, diffs {}]
(cond
(and (seq exp) (seq act) (not= e a))
(let [d (diff e a :recur)
diffs (cond
(diff-elem? d)
(assoc diffs [i] d)
(diff? d) (map-keys #(vec (cons i %)) d)
(empty? d) diffs
:else (throw (ex-info "This should not have happened"
{:d d :exp exp :act act})))]
(recur is es as diffs))
(and (seq exp) (>= i act-count))
(recur is es as (assoc diffs [i] (diff-elem e nf)))
(and (>= i exp-count) (seq act))
(recur is es as (assoc diffs [i] (diff-elem nf a)))
(and (>= i exp-count) (>= i act-count)) diffs
:else (recur is es as diffs)))))
(defn set-diff [exp act]
(let [missing-from-act (set/difference act exp)
missing-from-exp (set/difference exp act)]
(if (or (seq missing-from-act) (seq missing-from-exp))
(diff-elem missing-from-exp missing-from-act)
(diff-elem))))
(defn diff [exp act & [opt]]
(let [recur? (#{:recur} opt)]
(cond->
(cond
(every? map? [exp act])
(map-diff (vec (set (mapcat keys [exp act])))
exp act)
(every? string? [exp act])
(diff-elem exp act)
(every? set? [exp act])
(cond->> (set-diff exp act)
(not recur?) (assoc {} []))
(every? sequential? [exp act])
(seq-diff exp act)
(not= (type exp) (type act))
(diff-elem exp act)
(every? coll? [exp act])
(seq-diff exp act)
;; RECUR GUARD
(not recur?) {}
(not= exp act)
(diff-elem exp act)
:else [])
(not recur?) (#(cond->> % (diff-elem? %) (assoc {} []))))))
(defn patch
([x diffs]
(patch x diffs
{:get-exp
;; so we dont get noisy (& useless) diffs
(fn not-a-nf-diff [d]
(let [{:keys [exp]} (extract d)]
(when-not (= nf exp) exp)))}))
([x diffs {:keys [get-exp]
:or {get-exp (comp :exp extract)}}]
(let [list->lvec #(cond-> % (seq? %) (-> vec (with-meta {::list true})))
lvec->list #(cond-> % (and (vector? %) (-> % meta ::list true?)) vec)]
;; we turn lists into vectors and back so that we can assoc-in on them
(as-> x <>
(walk/prewalk list->lvec <>)
(reduce (fn [x d]
(let [path (seq (-> d extract :path))
exp (get-exp d)]
(cond
(not path) {[] exp} ;; empty path, top level diff
exp (assoc-in x path exp)
(and (nil? exp) (not (map? x))) (assoc-in x path nil)
;; else drop the missing item from up a level
:else (if-let [path' (seq (drop-last path))]
(update-in x path' dissoc (last path))
(dissoc x (last path))))))
<> diffs)
(walk/prewalk lvec->list <>)))))
(defn compress [[x & _ :as coll]]
(let [diff* (partial apply diff)]
(->> coll
(partition 2 1)
(map (comp diff* reverse))
(cons x)
(into (empty coll)))))
(defn decompress [[x & xs :as coll]]
(->> (reductions patch x xs)
(into (empty coll))))