-
-
Notifications
You must be signed in to change notification settings - Fork 137
/
util.cljc
176 lines (144 loc) · 4.93 KB
/
util.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
(ns fulcro.util
(:refer-clojure :exclude [ident?])
(:require
[clojure.spec.alpha :as s]
clojure.walk
[fulcro.logging :as log]
#?(:clj
[clojure.spec.gen.alpha :as sg]))
#?(:clj
(:import (clojure.lang Atom))))
(defn force-children [x]
(cond->> x
(seq? x) (into [] (map force-children))))
(defn union?
#?(:cljs {:tag boolean})
[expr]
(let [expr (cond-> expr (seq? expr) first)]
(and (map? expr)
(map? (-> expr first second)))))
(defn join? [x]
#?(:cljs {:tag boolean})
(let [x (if (seq? x) (first x) x)]
(map? x)))
(defn ident?
"Returns true if x is an ident."
#?(:cljs {:tag boolean})
[x]
(and (vector? x)
(== 2 (count x))
(keyword? (nth x 0))))
(defn join-entry [expr]
(let [[k v] (if (seq? expr)
(ffirst expr)
(first expr))]
[(if (list? k) (first k) k) v]))
(defn join-key [expr]
(cond
(map? expr) (let [k (ffirst expr)]
(if (list? k)
(first k)
(ffirst expr)))
(seq? expr) (join-key (first expr))
:else expr))
(defn join-value [join]
(second (join-entry join)))
(defn mutation-join? [expr]
(and (join? expr) (symbol? (join-key expr))))
(defn unique-ident?
#?(:cljs {:tag boolean})
[x]
(and (ident? x) (= '_ (second x))))
(defn recursion?
#?(:cljs {:tag boolean})
[x]
(or #?(:clj (= '... x)
:cljs (symbol-identical? '... x))
(number? x)))
(defn mutation?
#?(:cljs {:tag boolean})
[expr]
(or (mutation-join? expr) (symbol? (cond-> expr (seq? expr) first))))
(defn mutation-key [expr]
{:pre [(symbol? (first expr))]}
(first expr))
(defn unique-key
"Get a unique string-based key. Never returns the same value."
[]
(let [s #?(:clj (java.util.UUID/randomUUID)
:cljs (random-uuid))]
(str s)))
(defn atom? [a] (instance? Atom a))
(defn deep-merge [& xs]
"Merges nested maps without overwriting existing keys."
(if (every? map? xs)
(apply merge-with deep-merge xs)
(last xs)))
(defn conform! [spec x]
(let [rt (s/conform spec x)]
(when (s/invalid? rt)
(throw (ex-info (s/explain-str spec x)
(s/explain-data spec x))))
rt))
(defn soft-invariant
"Logs the given message if v is false."
[v msg]
(when-not v (log/error "Invariant failed")))
#?(:clj
(def TRUE (s/with-gen (constantly true) sg/int)))
#?(:clj
(defn resolve-externs
"Ensures the given needs are loaded, and resolved. Updates inmap to include all of the function symbols that were requested
as namespaced symbols.
inmap - A map (nil/empty)
needs - A sequence: ([namespace [f1 f2]] ...)
Returns a map keyed by namespaced symbol whose value is the resolved function:
{namespace/f1 (fn ...)
namespace/h2 (fn ...)
...}
Logs a detailed error message if it fails.
"
[inmap needs]
(reduce (fn [m [nmspc fns]]
(try
(require nmspc)
(let [n (find-ns nmspc)
fn-keys (map #(symbol (name nmspc) (name %)) fns)
fnmap (zipmap fn-keys (map #(or (ns-resolve n %) (throw (ex-info "No such symbol" {:ns nmspc :s %}))) fns))]
(merge m fnmap))
(catch Exception e
(log/error (str "Failed to load functions from " nmspc ". Fulcro does not have hard dependencies on that library, and you must explicitly add the dependency to your project.")))))
(or inmap {})
needs)))
#?(:clj
(defn load-libs
"Load libraries in Clojure dynamcically. externs is an atom that will hold the resulting resolved FQ symbols. needs
is a list of needs as specified in `fulcro.util/resolve-externs`."
[externs needs]
(when (or (nil? @externs) (empty? @externs))
(swap! externs resolve-externs needs))))
#?(:clj
(defn build-invoke
"Builds a function that can invoke a fq symbol by name. The returned function:
- Ensures the specified needs are loaded (fast once loaded)
- Looks up the function (cached)
- Runs the function
externs is an empty atom (which will be populated to cache the resolved functions)
needs is a map as specified in `resolve-externs`
```
(def externs (atom nil))
(def invoke (fulcro.util/build-invoke externs '([bidi.bidi [bidi-match]])))
...
(invoke 'bidi.bidi/bidi-match routes uri :request-method method)
```
The generated invoke will attempt to load the function if it isn't yet loaded, and throws
an exception if the function isn't found.
The special fnsym 'noop will trigger loads without calling anything."
[externs needs]
(fn [fnsym & args]
(load-libs externs needs)
(when-not (= 'noop fnsym)
(if-let [f (get @externs fnsym)]
(apply f args)
(throw (ex-info "Dynamically loaded function not found. You forgot to add a dependency to your classpath."
{:sym fnsym})))))))