-
-
Notifications
You must be signed in to change notification settings - Fork 97
/
misc.clj
144 lines (129 loc) · 5.13 KB
/
misc.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
139
140
141
142
143
144
(ns nrepl.misc
"Misc utilities used in nREPL's implementation (potentially also
useful for anyone extending it)."
{:author "Chas Emerick"}
(:refer-clojure :exclude [requiring-resolve])
(:require [clojure.java.io :as io]))
(defn log
[ex-or-msg & msgs]
(let [ex (when (instance? Throwable ex-or-msg) ex-or-msg)
msgs (if ex msgs (filter identity (cons ex-or-msg msgs)))]
(binding [*out* *err*]
(apply println "ERROR:" msgs)
(when ex (.printStackTrace ^Throwable ex)))))
(defmacro noisy-future
"Executes body in a future, logging any exceptions that make it to the
top level."
[& body]
`(future
(try
~@body
(catch Throwable ex#
(log ex#)
(throw ex#)))))
(defmacro returning
"Executes `body`, returning `x`."
{:style/indent 1}
[x & body]
`(let [x# ~x] ~@body x#))
(defn uuid
"Returns a new UUID string."
[]
(str (java.util.UUID/randomUUID)))
(defn response-for
"Returns a map containing the :session and :id from the \"request\" `msg`
as well as all entries specified in `response-data`, which can be one
or more maps (which will be merged), *or* key-value pairs.
(response-for msg :status :done :value \"5\")
(response-for msg {:status :interrupted})
The :session value in `msg` may be any Clojure reference type (to accommodate
likely implementations of sessions) that has an :id slot in its metadata,
or a string."
[{:keys [session id]} & response-data]
{:pre [(seq response-data)]}
(let [{:keys [status] :as response} (if (map? (first response-data))
(reduce merge response-data)
(apply hash-map response-data))
response (if (not status)
response
(assoc response :status (if (coll? status)
status
#{status})))
basis (merge (when id {:id id})
;; AReference should make this suitable for any session implementation?
(when session {:session (if (instance? clojure.lang.AReference session)
(-> session meta :id)
session)}))]
(merge basis response)))
(defn requiring-resolve
"Resolves namespace-qualified sym per 'resolve'. If initial resolve fails,
attempts to require sym's namespace and retries. Returns nil if sym could not
be resolved."
[sym & [log?]]
(or (resolve sym)
(try
(require (symbol (namespace sym)))
(resolve sym)
(catch Exception e
(when log?
(log e))))))
(defmacro with-session-classloader
"This macro does two things:
1. If the session has a classloader set, then execute the body using that.
This is typically used to trigger the sideloader, when active.
2. Bind `clojure.lang.Compiler/LOADER` to the context classloader, which
might also be the sideloader. This is required to get hotloading with
pomegranate working under certain conditions."
[session & body]
`(let [ctxcl# (.getContextClassLoader (Thread/currentThread))
alt-cl# (when-let [classloader# (:classloader (meta ~session))]
(classloader#))
cl# (or alt-cl# ctxcl#)]
(if (= ctxcl# cl#)
(with-bindings {clojure.lang.Compiler/LOADER cl#}
~@body)
(do
(.setContextClassLoader (Thread/currentThread) cl#)
(try
(with-bindings {clojure.lang.Compiler/LOADER cl#}
~@body)
(finally
(.setContextClassLoader (Thread/currentThread) ctxcl#)))))))
(defn java-8?
"Util to check if we are using Java 8. Useful for features that behave
differently after version 8."
[]
(.startsWith (System/getProperty "java.runtime.version")
"1.8"))
(def safe-var-metadata
"A list of var metadata attributes are safe to return to the clients.
We need to guard ourselves against EDN data that's not encodeable/decodable
with bencode. We also optimize the response payloads by not returning
redundant metadata."
[:ns :name :doc :file :arglists :forms :macro :special-form
:protocol :line :column :added :deprecated :resource])
(defn- handle-file-meta
"Convert :file metadata to string.
Typically `value` would be a string, a File or an URL."
[value]
(when value
(str (if (string? value)
;; try to convert relative file paths like "clojure/core.clj"
;; to absolute file paths
(or (io/resource value) value)
;; If :file is a File or URL object we just return it as is
;; and convert it to string
value))))
(defn sanitize-meta
"Sanitize a Clojure metadata map such that it can be bencoded."
[m]
(-> m
(select-keys safe-var-metadata)
(update :ns str)
(update :name str)
(update :protocol str)
(update :file handle-file-meta)
(cond-> (:macro m) (update :macro str))
(cond-> (:special-form m) (update :special-form str))
(assoc :arglists-str (str (:arglists m)))
(cond-> (:arglists m) (update :arglists str))))