/
templating.cljs
215 lines (174 loc) · 7.7 KB
/
templating.cljs
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
(ns devtools.formatters.templating
(:require-macros [devtools.oops :refer [oget oset ocall oapply safe-call unchecked-aget]])
(:require [clojure.walk :refer [prewalk]]
[devtools.util :refer [pprint-str]]
[devtools.protocols :refer [ITemplate IGroup ISurrogate IFormat]]
[devtools.formatters.helpers :refer [pref cljs-value?]]
[devtools.formatters.state :refer [get-current-state prevent-recursion?]]
[clojure.string :as string]))
; -- object marking support -------------------------------------------------------------------------------------------------
(defn mark-as-group! [value]
(specify! value IGroup)
value)
(defn group? [value]
(satisfies? IGroup value))
(defn mark-as-template! [value]
(specify! value ITemplate)
value)
(defn template? [value]
(satisfies? ITemplate value))
(defn mark-as-surrogate! [value]
(specify! value ISurrogate)
value)
(defn surrogate? [value]
(satisfies? ISurrogate value))
(defn reference? [value]
(and (group? value)
(= (unchecked-aget value 0) "object")))
; ---------------------------------------------------------------------------------------------------------------------------
(defn make-group [& items]
(let [group (mark-as-group! #js [])]
(doseq [item items]
(if (some? item)
(if (coll? item)
(.apply (unchecked-aget group "push") group (mark-as-group! (into-array item))) ; convenience helper to splat cljs collections
(.push group (pref item)))))
group))
(defn make-template
[tag style & children]
(let [tag (pref tag)
style (pref style)
template (mark-as-template! #js [tag (if (empty? style)
#js {}
#js {"style" style})])]
(doseq [child children]
(if (some? child)
(if (coll? child)
(.apply (unchecked-aget template "push") template (mark-as-template! (into-array (keep pref child)))) ; convenience helper to splat cljs collections
(if-let [child-value (pref child)]
(.push template child-value)))))
template))
(defn concat-templates! [template & templates]
(mark-as-template! (.apply (oget template "concat") template (into-array (map into-array (keep pref templates))))))
(defn extend-template! [template & args]
(concat-templates! template args))
(defn make-surrogate
; passing :target as body means that targt object body should be rendered using standard templates
; see <surrogate-body> in markup.cljs
([object] (make-surrogate object nil))
([object header] (make-surrogate object header nil))
([object header body] (make-surrogate object header body 0))
([object header body start-index]
(mark-as-surrogate! (js-obj
"target" object
"header" header
"body" body
"startIndex" (or start-index 0)))))
(defn get-surrogate-target [surrogate]
{:pre [(surrogate? surrogate)]}
(oget surrogate "target"))
(defn get-surrogate-header [surrogate]
{:pre [(surrogate? surrogate)]}
(oget surrogate "header"))
(defn get-surrogate-body [surrogate]
{:pre [(surrogate? surrogate)]}
(oget surrogate "body"))
(defn get-surrogate-start-index [surrogate]
{:pre [(surrogate? surrogate)]}
(oget surrogate "startIndex"))
(defn make-reference [object & [state-override-fn]]
{:pre [(or (nil? state-override-fn) (fn? state-override-fn))]}
(if (nil? object)
; this code is duplicated in markup.cljs <nil>
(make-template :span :nil-style :nil-label)
(let [sub-state (if (some? state-override-fn)
(state-override-fn (get-current-state))
(get-current-state))]
(make-group "object" #js {"object" object
"config" sub-state}))))
(defn make-annotation [data markups]
(apply make-group "annotation" (clj->js data) markups))
; -- JSON ML support --------------------------------------------------------------------------------------------------------
; a renderer from hiccup-like data markup to json-ml
;
; [[tag style] child1 child2 ...] -> #js [tag #js {"style" ...} child1 child2 ...]
;
(declare render-json-ml*)
(def ^:dynamic *current-render-stack* [])
(def ^:dynamic *current-render-path* [])
(defn print-preview [markup]
(binding [*print-level* 1]
(pr-str markup)))
(defn add-stack-separators [stack]
(interpose "-------------" stack))
(defn replace-fns-with-markers [stack]
(let [f (fn [v]
(if (fn? v)
"##fn##"
v))]
(prewalk f stack)))
(defn pprint-render-calls [stack]
(map pprint-str stack))
(defn pprint-render-stack [stack]
(string/join "\n" (-> stack
reverse
replace-fns-with-markers
pprint-render-calls
add-stack-separators)))
(defn pprint-render-path [path]
(pprint-str path))
(defn assert-markup-error [msg]
(assert false (str msg "\n"
"Render path: " (pprint-render-path *current-render-path*) "\n"
"Render stack:\n"
(pprint-render-stack *current-render-stack*))))
(defn surrogate-markup? [markup]
(and (sequential? markup) (= (first markup) "surrogate")))
(defn render-special [name args]
(case name
"surrogate" (let [obj (first args)
converted-args (map render-json-ml* (rest args))]
(apply make-surrogate (concat [obj] converted-args)))
"reference" (let [obj (first args)
converted-obj (if (surrogate-markup? obj) (render-json-ml* obj) obj)]
(apply make-reference (concat [converted-obj] (rest args))))
"annotation" (let [data (first args)
converted-args (map render-json-ml* (rest args))]
(make-annotation data converted-args))
(assert-markup-error (str "no matching special tag name: '" name "'"))))
(defn emptyish? [v]
(if (or (seqable? v) (array? v) (string? v))
(empty? v)
false))
(defn render-subtree [tag children]
(let [[html-tag style] tag]
(apply make-template html-tag style (map render-json-ml* (remove emptyish? (map pref children))))))
(defn render-json-ml* [markup]
(if-not (sequential? markup)
markup
(binding [*current-render-path* (conj *current-render-path* (first markup))]
(let [tag (pref (first markup))]
(cond
(string? tag) (render-special tag (rest markup))
(sequential? tag) (render-subtree tag (rest markup))
:else (assert-markup-error (str "invalid json-ml markup at " (print-preview markup) ":")))))))
(defn render-json-ml [markup]
(binding [*current-render-stack* (conj *current-render-stack* markup)
*current-render-path* (conj *current-render-path* "<render-json-ml>")]
(render-json-ml* markup)))
; -- template rendering -----------------------------------------------------------------------------------------------------
(defn ^:dynamic assert-failed-markup-rendering [initial-value value]
(assert false (str "result of markup rendering must be a template,\n"
"resolved to " (pprint-str value)
"initial value: " (pprint-str initial-value))))
(defn render-markup* [initial-value value]
(cond
(fn? value) (recur initial-value (value))
(keyword? value) (recur initial-value (pref value))
(sequential? value) (recur initial-value (render-json-ml value))
(template? value) value
(surrogate? value) value
(reference? value) value
:else (assert-failed-markup-rendering initial-value value)))
(defn render-markup [value]
(render-markup* value value))