-
-
Notifications
You must be signed in to change notification settings - Fork 97
/
load_file.clj
113 lines (106 loc) · 5.47 KB
/
load_file.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
(ns nrepl.middleware.load-file
{:author "Chas Emerick"}
(:require
[nrepl.middleware :as middleware :refer [set-descriptor!]]
[nrepl.middleware.caught :as caught]
[nrepl.middleware.interruptible-eval :as eval]
[nrepl.middleware.print :as print])
(:import nrepl.transport.Transport))
;; need to hold file contents "out of band" so as to avoid JVM method
;; size limitations (cannot eval an expression larger than some size
;; [64k?]), so the naive approach of just interpolating file contents
;; into an expression to be evaluated doesn't work
;; see http://code.google.com/p/counterclockwise/issues/detail?id=429
;; and http://groups.google.com/group/clojure/browse_thread/thread/f54044da06b9939f
(defonce ^{:private true
:doc "An atom that temporarily holds the contents of files to
be loaded."} file-contents (atom {}))
(defn- load-large-file-code
"A variant of `load-file-code` that returns an
expression that will only work if evaluated within the same process
where it was called. Here to work around the JVM method size limit
so that (by default, for those tools using the load-file middleware)
loading files of any size will work when the nREPL server is running
remotely or locally."
[file file-path file-name]
;; mini TTL impl so that any code orphaned by errors that occur
;; between here and the evaluation of the Compiler/load expression
;; below are cleaned up on subsequent loads
(let [t (System/currentTimeMillis)
file-key ^{:t t} [file-path (gensym)]]
(swap! file-contents
(fn [file-contents]
(let [expired-keys
(filter
(comp #(and %
(< 10000 (- (System/currentTimeMillis) %)))
:t meta)
(keys file-contents))]
(assoc (apply dissoc file-contents expired-keys)
file-key file))))
(binding [*print-length* nil
*print-level* nil]
(pr-str `(try
(clojure.lang.Compiler/load
(java.io.StringReader. (@@(var file-contents) '~file-key))
~file-path
~file-name)
(finally
(swap! @(var file-contents) dissoc '~file-key)))))))
(defn ^{:dynamic true} load-file-code
"Given the contents of a file, its _source-path-relative_ path,
and its filename, returns a string of code containing a single
expression that, when evaluated, will load those contents with
appropriate filename references and line numbers in metadata, etc.
Note that because a single expression is produced, very large
file loads will fail due to the JVM method size limitation.
In such cases, see `load-large-file-code'`."
[file file-path file-name]
(apply format
"(clojure.lang.Compiler/load (java.io.StringReader. %s) %s %s)"
(map (fn [item]
(binding [*print-length* nil
*print-level* nil]
(pr-str item)))
[file file-path file-name])))
(defn wrap-load-file
"Middleware that evaluates a file's contents, as per load-file,
but with all data supplied in the sent message (i.e. safe for use
with remote REPL environments).
This middleware depends on the availability of an :op \"eval\"
middleware below it (such as interruptible-eval)."
[h]
(fn [{:keys [op file file-name file-path ^Transport transport] :as msg}]
(if (not= op "load-file")
(h msg)
(h (assoc (dissoc msg :file :file-name :file-path)
:op "eval"
:code ((if (thread-bound? #'load-file-code)
load-file-code
load-large-file-code)
file file-path file-name)
:transport (reify Transport
(recv [_this] (.recv transport))
(recv [_this timeout] (.recv transport timeout))
(send [this resp]
;; *ns* is always 'user' after loading a file, so
;; *remove it to avoid confusing tools that assume any
;; *:ns always reports *ns*
(.send transport (dissoc resp :ns))
this)))))))
(set-descriptor! #'wrap-load-file
{:requires #{#'caught/wrap-caught #'print/wrap-print}
:expects #{"eval"}
:handles {"load-file"
{:doc "Loads a body of code, using supplied path and filename info to set source file and line number metadata. Delegates to underlying \"eval\" middleware/handler."
:requires {"file" "Full contents of a file of code."}
:optional (merge caught/wrap-caught-optional-arguments
print/wrap-print-optional-arguments
{"file-path" "Source-path-relative path of the source file, e.g. clojure/java/io.clj"
"file-name" "Name of source file, e.g. io.clj"})
:returns (-> (meta #'eval/interruptible-eval)
::middleware/descriptor
:handles
(get "eval")
:returns
(dissoc "ns"))}}})