This repository has been archived by the owner on Jul 27, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathhooke.clj
138 lines (112 loc) · 3.79 KB
/
hooke.clj
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
(ns robert.hooke
"Hooke your functions!
(defn examine [x]
(println x))
(defn microscope
\"The keen powers of observation enabled by Robert Hooke allow
for a closer look at any object!\"
[f x]
(f (.toUpperCase (str x))))
(defn doubler [f & args]
(apply f args)
(apply f args))
(defn telescope [f x]
(f (apply str (interpose \" \" x))))
(add-hook #'examine #'microscope)
(add-hook #'examine #'doubler)
(add-hook #'examine #'telescope)
;; Now when we examine something:
(examine \"something\")
> S O M E T H I N G
> S O M E T H I N G
Use the add-hook function to wrap a function in your a hook.")
(defn- hooks [v]
(-> @v meta ::hooks))
(defn- original [v]
(-> @v meta ::original))
(defn- compose-hooks [f1 f2]
(fn [& args]
;; TODO: tracing
(apply f2 f1 args)))
(defn- join-hooks [original hooks]
(reduce compose-hooks original hooks))
(defn- run-hooks [hooks original args]
(apply (join-hooks original hooks) args))
(defn- prepare-for-hooks [v]
(when-not (hooks v)
(let [hooks (atom {})]
(alter-var-root v (fn [original]
(with-meta
(fn [& args]
(run-hooks (vals @hooks) original args))
(assoc (meta original)
::hooks hooks
::original original)))))))
(defonce hook-scopes [])
(defn start-scope []
(locking hook-scopes
(alter-var-root #'hook-scopes conj {})))
(defn- scope-update-fn
[scopes target-var]
(conj
(pop scopes)
(update-in (peek scopes) [target-var] #(if % % @(hooks target-var)))))
(defn- possibly-record-in-scope
[target-var]
(locking hook-scopes
(when (seq hook-scopes)
(alter-var-root #'hook-scopes scope-update-fn target-var))))
(defn end-scope []
(locking hook-scopes
(let [head (peek hook-scopes)]
(alter-var-root #'hook-scopes pop)
(doseq [[var old-hooks] head]
(reset! (hooks var) old-hooks)))))
(defmacro with-scope
"Defines a scope which records any change to hooks during the dynamic extent
of its body, and restores hooks to their original state on exit of the scope."
[& body]
`(try
(start-scope)
~@body
(finally (end-scope))))
(defn add-hook
"Add a hook function f to target-var. Hook functions are passed the
target function and all their arguments and must apply the target to
the args if they wish to continue execution."
([target-var f]
(add-hook target-var f f))
([target-var key f]
(prepare-for-hooks target-var)
(possibly-record-in-scope target-var)
(swap! (hooks target-var) assoc key f)))
(defn- clear-hook-mechanism [target-var]
(alter-var-root target-var
(constantly (original target-var))))
(defn remove-hook
"Remove hook identified by key from target-var."
[target-var key]
(when-let [hooks (hooks target-var)]
(swap! hooks dissoc key)
(when (empty? @hooks)
(clear-hook-mechanism target-var))))
(defn clear-hooks
"Remove all hooks from target-var."
[target-var]
(when-let [hooks (hooks target-var)]
(swap! hooks empty)
(clear-hook-mechanism target-var)))
(defmacro prepend [target-var & body]
`(add-hook (var ~target-var) (fn [f# & args#]
~@body
(apply f# args#))))
(defmacro append [target-var & body]
`(add-hook (var ~target-var) (fn [f# & args#]
(let [val# (apply f# args#)]
~@body
val#))))
(defmacro with-hooks-disabled [f & body]
`(do (when-not (#'hooks (var ~f))
(throw (Exception. (str "No hooks on " ~f))))
(with-redefs [~f (#'original (var ~f))]
~@body)))