clementine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/README.md +52 -0
- data/Rakefile +1 -0
- data/clementine.gemspec +23 -0
- data/lib/clementine.rb +27 -0
- data/lib/clementine/clementine_rails.rb +8 -0
- data/lib/clementine/clojurescript_engine.rb +49 -0
- data/lib/clementine/clojurescript_engine_mri.rb +65 -0
- data/lib/clementine/clojurescript_template.rb +21 -0
- data/lib/clementine/options.rb +9 -0
- data/lib/clementine/version.rb +3 -0
- data/test/clojurescript_engine_test.rb +46 -0
- data/test/options_test.rb +22 -0
- data/vendor/assets/bin/cljsc.clj +21 -0
- data/vendor/assets/lib/clojure.jar +0 -0
- data/vendor/assets/lib/compiler.jar +0 -0
- data/vendor/assets/lib/goog.jar +0 -0
- data/vendor/assets/lib/js.jar +0 -0
- data/vendor/assets/src/clj/cljs/closure.clj +823 -0
- data/vendor/assets/src/clj/cljs/compiler.clj +1341 -0
- data/vendor/assets/src/clj/cljs/core.clj +702 -0
- data/vendor/assets/src/clj/cljs/repl.clj +162 -0
- data/vendor/assets/src/clj/cljs/repl/browser.clj +341 -0
- data/vendor/assets/src/clj/cljs/repl/rhino.clj +170 -0
- data/vendor/assets/src/cljs/cljs/core.cljs +3330 -0
- data/vendor/assets/src/cljs/cljs/nodejs.cljs +11 -0
- data/vendor/assets/src/cljs/cljs/nodejs_externs.js +2 -0
- data/vendor/assets/src/cljs/cljs/nodejscli.cljs +9 -0
- data/vendor/assets/src/cljs/cljs/reader.cljs +360 -0
- data/vendor/assets/src/cljs/clojure/browser/dom.cljs +106 -0
- data/vendor/assets/src/cljs/clojure/browser/event.cljs +100 -0
- data/vendor/assets/src/cljs/clojure/browser/net.cljs +182 -0
- data/vendor/assets/src/cljs/clojure/browser/repl.cljs +109 -0
- data/vendor/assets/src/cljs/clojure/set.cljs +162 -0
- data/vendor/assets/src/cljs/clojure/string.cljs +160 -0
- data/vendor/assets/src/cljs/clojure/walk.cljs +94 -0
- data/vendor/assets/src/cljs/clojure/zip.cljs +291 -0
- metadata +103 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
;; Copyright (c) Rich Hickey. All rights reserved.
|
2
|
+
;; The use and distribution terms for this software are covered by the
|
3
|
+
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
4
|
+
;; which can be found in the file epl-v10.html at the root of this distribution.
|
5
|
+
;; By using this software in any fashion, you are agreeing to be bound by
|
6
|
+
;; the terms of this license.
|
7
|
+
;; You must not remove this notice, or any other, from this software.
|
8
|
+
|
9
|
+
(ns cljs.repl
|
10
|
+
(:refer-clojure :exclude [load-file])
|
11
|
+
(:require [clojure.string :as string]
|
12
|
+
[clojure.java.io :as io]
|
13
|
+
[cljs.compiler :as comp]
|
14
|
+
[cljs.closure :as cljsc]))
|
15
|
+
|
16
|
+
(def ^:dynamic *cljs-verbose* false)
|
17
|
+
|
18
|
+
(defprotocol IJavaScriptEnv
|
19
|
+
(-setup [this] "initialize the environment")
|
20
|
+
(-evaluate [this filename line js] "evaluate a javascript string")
|
21
|
+
(-load [this ns url] "load code at url into the environment")
|
22
|
+
(-tear-down [this] "dispose of the environment"))
|
23
|
+
|
24
|
+
(defn load-namespace
|
25
|
+
"Load a namespace and all of its dependencies into the evaluation environment.
|
26
|
+
The environment is responsible for ensuring that each namespace is loaded once and
|
27
|
+
only once."
|
28
|
+
[repl-env sym]
|
29
|
+
(let [sym (if (and (seq? sym)
|
30
|
+
(= (first sym) 'quote))
|
31
|
+
(second sym)
|
32
|
+
sym)
|
33
|
+
opts {:output-dir (get repl-env :working-dir ".repl")}
|
34
|
+
deps (->> (cljsc/add-dependencies opts {:requires [(name sym)] :type :seed})
|
35
|
+
(remove (comp #{["goog"]} :provides))
|
36
|
+
(remove (comp #{:seed} :type))
|
37
|
+
(map #(select-keys % [:provides :url])))]
|
38
|
+
(doseq [{:keys [url provides]} deps]
|
39
|
+
(-load repl-env provides url))))
|
40
|
+
|
41
|
+
(defn- load-dependencies
|
42
|
+
[repl-env requires]
|
43
|
+
(doseq [ns requires]
|
44
|
+
(load-namespace repl-env ns)))
|
45
|
+
|
46
|
+
(defn- display-error
|
47
|
+
([ret form]
|
48
|
+
(display-error ret form (constantly nil)))
|
49
|
+
([ret form f]
|
50
|
+
(when-not (and (seq? form) (= 'ns (first form)))
|
51
|
+
(f)
|
52
|
+
(println (:value ret))
|
53
|
+
(when-let [st (:stacktrace ret)]
|
54
|
+
(println st)))))
|
55
|
+
|
56
|
+
(defn evaluate-form
|
57
|
+
"Evaluate a ClojureScript form in the JavaScript environment. Returns a
|
58
|
+
string which is the ClojureScript return value. This string may or may
|
59
|
+
not be readable by the Clojure reader."
|
60
|
+
([repl-env env filename form]
|
61
|
+
(evaluate-form repl-env env filename form identity))
|
62
|
+
([repl-env env filename form wrap]
|
63
|
+
(try
|
64
|
+
(let [ast (comp/analyze env form)
|
65
|
+
js (comp/emits ast)
|
66
|
+
wrap-js (comp/emits (comp/analyze env (wrap form)))]
|
67
|
+
(when (= (:op ast) :ns)
|
68
|
+
(load-dependencies repl-env (vals (:requires ast))))
|
69
|
+
(when *cljs-verbose*
|
70
|
+
(print js))
|
71
|
+
(let [ret (-evaluate repl-env filename (:line (meta form)) wrap-js)]
|
72
|
+
(case (:status ret)
|
73
|
+
;;we eat ns errors because we know goog.provide() will throw when reloaded
|
74
|
+
;;TODO - file bug with google, this is bs error
|
75
|
+
;;this is what you get when you try to 'teach new developers'
|
76
|
+
;;via errors (goog/base.js 104)
|
77
|
+
:error (display-error ret form)
|
78
|
+
:exception (display-error ret form
|
79
|
+
#(prn "Error evaluating:" form :as js))
|
80
|
+
:success (:value ret))))
|
81
|
+
(catch Throwable ex
|
82
|
+
(.printStackTrace ex)
|
83
|
+
(println (str ex))))))
|
84
|
+
|
85
|
+
(defn load-stream [repl-env filename stream]
|
86
|
+
(with-open [r (io/reader stream)]
|
87
|
+
(let [env {:ns (@comp/namespaces comp/*cljs-ns*) :context :statement :locals {}}
|
88
|
+
pbr (clojure.lang.LineNumberingPushbackReader. r)
|
89
|
+
eof (Object.)]
|
90
|
+
(loop [r (read pbr false eof false)]
|
91
|
+
(let [env (assoc env :ns (@comp/namespaces comp/*cljs-ns*))]
|
92
|
+
(when-not (identical? eof r)
|
93
|
+
(evaluate-form repl-env env filename r)
|
94
|
+
(recur (read pbr false eof false))))))))
|
95
|
+
|
96
|
+
(defn load-file
|
97
|
+
[repl-env f]
|
98
|
+
(binding [comp/*cljs-ns* 'cljs.user]
|
99
|
+
(let [res (if (= \/ (first f)) f (io/resource f))]
|
100
|
+
(assert res (str "Can't find " f " in classpath"))
|
101
|
+
(load-stream repl-env f res))))
|
102
|
+
|
103
|
+
(defn- wrap-fn [form]
|
104
|
+
(cond (and (seq? form) (= 'ns (first form))) identity
|
105
|
+
('#{*1 *2 *3} form) (fn [x] `(cljs.core.pr-str ~x))
|
106
|
+
:else (fn [x] `(cljs.core.pr-str
|
107
|
+
(let [ret# ~x]
|
108
|
+
(do (set! *3 *2)
|
109
|
+
(set! *2 *1)
|
110
|
+
(set! *1 ret#)
|
111
|
+
ret#))))))
|
112
|
+
|
113
|
+
(defn- eval-and-print [repl-env env form]
|
114
|
+
(let [ret (evaluate-form repl-env
|
115
|
+
(assoc env :ns (@comp/namespaces comp/*cljs-ns*))
|
116
|
+
"<cljs repl>"
|
117
|
+
form
|
118
|
+
(wrap-fn form))]
|
119
|
+
(try (prn (read-string ret))
|
120
|
+
(catch Exception e
|
121
|
+
(if (string? ret)
|
122
|
+
(println ret)
|
123
|
+
(prn nil))))))
|
124
|
+
|
125
|
+
(defn- read-next-form []
|
126
|
+
(try {:status :success :form (binding [*ns* (create-ns comp/*cljs-ns*)]
|
127
|
+
(read))}
|
128
|
+
(catch Exception e
|
129
|
+
(println (.getMessage e))
|
130
|
+
{:status :error})))
|
131
|
+
|
132
|
+
(defn repl
|
133
|
+
"Note - repl will reload core.cljs every time, even if supplied old repl-env"
|
134
|
+
[repl-env & {:keys [verbose warn-on-undeclared]}]
|
135
|
+
(prn "Type: " :cljs/quit " to quit")
|
136
|
+
(binding [comp/*cljs-ns* 'cljs.user
|
137
|
+
*cljs-verbose* verbose
|
138
|
+
comp/*cljs-warn-on-undeclared* warn-on-undeclared]
|
139
|
+
(let [env {:context :statement :locals {}}]
|
140
|
+
(-setup repl-env)
|
141
|
+
(loop []
|
142
|
+
(print (str "ClojureScript:" comp/*cljs-ns* "> "))
|
143
|
+
(flush)
|
144
|
+
(let [{:keys [status form]} (read-next-form)]
|
145
|
+
(cond
|
146
|
+
(= form :cljs/quit) :quit
|
147
|
+
|
148
|
+
(= status :error) (recur)
|
149
|
+
|
150
|
+
(and (seq? form) (= (first form) 'in-ns))
|
151
|
+
(do (set! comp/*cljs-ns* (second (second form))) (newline) (recur))
|
152
|
+
|
153
|
+
(and (seq? form) ('#{load-file clojure.core/load-file} (first form)))
|
154
|
+
(do (load-file repl-env (second form)) (newline) (recur))
|
155
|
+
|
156
|
+
(and (seq? form) ('#{load-namespace} (first form)))
|
157
|
+
(do (load-namespace repl-env (second form)) (newline) (recur))
|
158
|
+
|
159
|
+
:else
|
160
|
+
(do (eval-and-print repl-env env form) (recur)))))
|
161
|
+
(-tear-down repl-env))))
|
162
|
+
|
@@ -0,0 +1,341 @@
|
|
1
|
+
;; Copyright (c) Rich Hickey. All rights reserved.
|
2
|
+
;; The use and distribution terms for this software are covered by the
|
3
|
+
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
4
|
+
;; which can be found in the file epl-v10.html at the root of this distribution.
|
5
|
+
;; By using this software in any fashion, you are agreeing to be bound by
|
6
|
+
;; the terms of this license.
|
7
|
+
;; You must not remove this notice, or any other, from this software.
|
8
|
+
|
9
|
+
(ns cljs.repl.browser
|
10
|
+
(:refer-clojure :exclude [loaded-libs])
|
11
|
+
(:require [clojure.string :as str]
|
12
|
+
[clojure.java.io :as io]
|
13
|
+
[cljs.compiler :as comp]
|
14
|
+
[cljs.closure :as cljsc]
|
15
|
+
[cljs.repl :as repl])
|
16
|
+
(:import java.io.BufferedReader
|
17
|
+
java.io.BufferedWriter
|
18
|
+
java.io.InputStreamReader
|
19
|
+
java.io.OutputStreamWriter
|
20
|
+
java.net.Socket
|
21
|
+
java.net.ServerSocket
|
22
|
+
cljs.repl.IJavaScriptEnv))
|
23
|
+
|
24
|
+
(defonce server-state (atom {:socket nil
|
25
|
+
:connection nil
|
26
|
+
:promised-conn nil
|
27
|
+
:return-value-fn nil
|
28
|
+
:client-js nil}))
|
29
|
+
|
30
|
+
(def loaded-libs (atom #{}))
|
31
|
+
|
32
|
+
(defn- connection
|
33
|
+
"Promise to return a connection when one is available. If a
|
34
|
+
connection is not available, store the promise in server-state."
|
35
|
+
[]
|
36
|
+
(let [p (promise)
|
37
|
+
conn (:connection @server-state)]
|
38
|
+
(if (and conn (not (.isClosed conn)))
|
39
|
+
(do (deliver p conn)
|
40
|
+
p)
|
41
|
+
(do (swap! server-state (fn [old] (assoc old :promised-conn p)))
|
42
|
+
p))))
|
43
|
+
|
44
|
+
(defn- set-connection
|
45
|
+
"Given a new available connection, either use it to deliver the
|
46
|
+
connection which was promised or store the connection for later
|
47
|
+
use."
|
48
|
+
[conn]
|
49
|
+
(if-let [promised-conn (:promised-conn @server-state)]
|
50
|
+
(do (swap! server-state (fn [old] (-> old
|
51
|
+
(assoc :connection nil)
|
52
|
+
(assoc :promised-conn nil))))
|
53
|
+
(deliver promised-conn conn))
|
54
|
+
(swap! server-state (fn [old] (assoc old :connection conn)))))
|
55
|
+
|
56
|
+
(defn- set-return-value-fn
|
57
|
+
"Save the return value function which will be called when the next
|
58
|
+
return value is received."
|
59
|
+
[f]
|
60
|
+
(swap! server-state (fn [old] (assoc old :return-value-fn f))))
|
61
|
+
|
62
|
+
(defn- status-line [status]
|
63
|
+
(case status
|
64
|
+
200 "HTTP/1.1 200 OK"
|
65
|
+
404 "HTTP/1.1 404 Not Found"
|
66
|
+
"HTTP/1.1 500 Error"))
|
67
|
+
|
68
|
+
(defn send-and-close
|
69
|
+
"Use the passed connection to send a form to the browser. Send a
|
70
|
+
proper HTTP response."
|
71
|
+
([conn status form]
|
72
|
+
(send-and-close conn status form "text/html"))
|
73
|
+
([conn status form content-type]
|
74
|
+
(let [utf-8-form (.getBytes form "UTF-8")
|
75
|
+
content-length (count utf-8-form)
|
76
|
+
headers (map #(.getBytes (str % "\r\n"))
|
77
|
+
[(status-line status)
|
78
|
+
"Server: ClojureScript REPL"
|
79
|
+
(str "Content-Type: "
|
80
|
+
content-type
|
81
|
+
"; charset=utf-8")
|
82
|
+
(str "Content-Length: " content-length)
|
83
|
+
""])]
|
84
|
+
(with-open [os (.getOutputStream conn)]
|
85
|
+
(do (doseq [header headers]
|
86
|
+
(.write os header 0 (count header)))
|
87
|
+
(.write os utf-8-form 0 content-length)
|
88
|
+
(.flush os)
|
89
|
+
(.close conn))))))
|
90
|
+
|
91
|
+
(defn send-404 [conn path]
|
92
|
+
(send-and-close conn 404
|
93
|
+
(str "<html><body>"
|
94
|
+
"<h2>Page not found</h2>"
|
95
|
+
"No page " path " found on this server."
|
96
|
+
"</body></html>")
|
97
|
+
"text/html"))
|
98
|
+
|
99
|
+
(defn send-for-eval
|
100
|
+
"Given a form and a return value function, send the form to the
|
101
|
+
browser for evaluation. The return value function will be called
|
102
|
+
when the return value is received."
|
103
|
+
([form return-value-fn]
|
104
|
+
(send-for-eval @(connection) form return-value-fn))
|
105
|
+
([conn form return-value-fn]
|
106
|
+
(do (set-return-value-fn return-value-fn)
|
107
|
+
(send-and-close conn 200 form "text/javascript"))))
|
108
|
+
|
109
|
+
(defn- return-value
|
110
|
+
"Called by the server when a return value is received."
|
111
|
+
[val]
|
112
|
+
(when-let [f (:return-value-fn @server-state)]
|
113
|
+
(f val)))
|
114
|
+
|
115
|
+
(defn parse-headers
|
116
|
+
"Parse the headers of an HTTP POST request."
|
117
|
+
[header-lines]
|
118
|
+
(apply hash-map
|
119
|
+
(mapcat
|
120
|
+
(fn [line]
|
121
|
+
(let [[k v] (str/split line #":" 2)]
|
122
|
+
[(keyword (str/lower-case k)) (str/triml v)]))
|
123
|
+
header-lines)))
|
124
|
+
|
125
|
+
(comment
|
126
|
+
|
127
|
+
(parse-headers
|
128
|
+
["Host: www.mysite.com"
|
129
|
+
"User-Agent: Mozilla/4.0"
|
130
|
+
"Content-Length: 27"
|
131
|
+
"Content-Type: application/x-www-form-urlencoded"])
|
132
|
+
)
|
133
|
+
|
134
|
+
;;; assumes first line already consumed
|
135
|
+
(defn read-headers [rdr]
|
136
|
+
(loop [next-line (.readLine rdr)
|
137
|
+
header-lines []]
|
138
|
+
(if (= "" next-line)
|
139
|
+
header-lines ;we're done reading headers
|
140
|
+
(recur (.readLine rdr) (conj header-lines next-line)))))
|
141
|
+
|
142
|
+
(defn read-post [line rdr]
|
143
|
+
(let [[_ path _] (str/split line #" ")
|
144
|
+
headers (parse-headers (read-headers rdr))
|
145
|
+
content-length (Integer/parseInt (:content-length headers))
|
146
|
+
content (char-array content-length)]
|
147
|
+
(io! (.read rdr content 0 content-length)
|
148
|
+
{:method :post
|
149
|
+
:path path
|
150
|
+
:headers headers
|
151
|
+
:content (String. content)})))
|
152
|
+
|
153
|
+
(defn read-get [line rdr]
|
154
|
+
(let [[_ path _] (str/split line #" ")
|
155
|
+
headers (parse-headers (read-headers rdr))]
|
156
|
+
{:method :get
|
157
|
+
:path path
|
158
|
+
:headers headers}))
|
159
|
+
|
160
|
+
(defn read-request [rdr]
|
161
|
+
(let [line (.readLine rdr)]
|
162
|
+
(cond (.startsWith line "POST") (read-post line rdr)
|
163
|
+
(.startsWith line "GET") (read-get line rdr)
|
164
|
+
:else {:method :unknown :content line})))
|
165
|
+
|
166
|
+
(defn repl-client-js []
|
167
|
+
(slurp @(:client-js @server-state)))
|
168
|
+
|
169
|
+
(defn send-repl-client-page
|
170
|
+
[opts conn request]
|
171
|
+
(send-and-close conn 200
|
172
|
+
(str "<html><head><meta charset=\"UTF-8\"></head><body>
|
173
|
+
<script type=\"text/javascript\">"
|
174
|
+
(repl-client-js)
|
175
|
+
"</script>"
|
176
|
+
"<script type=\"text/javascript\">
|
177
|
+
clojure.browser.repl.client.start(\"http://" (-> request :headers :host) "\");
|
178
|
+
</script>"
|
179
|
+
"</body></html>")
|
180
|
+
"text/html"))
|
181
|
+
|
182
|
+
(defn handle-get [opts conn request]
|
183
|
+
(let [path (:path request)]
|
184
|
+
(if (.startsWith path "/repl")
|
185
|
+
(send-repl-client-page opts conn request)
|
186
|
+
(send-404 conn (:path request)))))
|
187
|
+
|
188
|
+
(declare browser-eval)
|
189
|
+
|
190
|
+
(def ordering (agent {:expecting nil :fns {}}))
|
191
|
+
|
192
|
+
(defmulti handle-post (fn [_ m] (:type m)))
|
193
|
+
|
194
|
+
(defmethod handle-post :ready [conn _]
|
195
|
+
(do (reset! loaded-libs #{})
|
196
|
+
(send ordering (fn [_] {:expecting nil :fns {}}))
|
197
|
+
(send-for-eval conn
|
198
|
+
(cljsc/-compile
|
199
|
+
'[(ns cljs.user)
|
200
|
+
(set! *print-fn* clojure.browser.repl/repl-print)] {})
|
201
|
+
identity)))
|
202
|
+
|
203
|
+
(defn add-in-order [{:keys [expecting fns]} order f]
|
204
|
+
{:expecting (or expecting order) :fns (assoc fns order f)})
|
205
|
+
|
206
|
+
(defn run-in-order [{:keys [expecting fns]}]
|
207
|
+
(loop [order expecting
|
208
|
+
fns fns]
|
209
|
+
(if-let [f (get fns order)]
|
210
|
+
(do (f)
|
211
|
+
(recur (inc order) (dissoc fns order)))
|
212
|
+
{:expecting order :fns fns})))
|
213
|
+
|
214
|
+
(defn constrain-order
|
215
|
+
"Elements to be printed in the REPL will arrive out of order. Ensure
|
216
|
+
that they are printed in the correct order."
|
217
|
+
[order f]
|
218
|
+
(send-off ordering add-in-order order f)
|
219
|
+
(send-off ordering run-in-order))
|
220
|
+
|
221
|
+
(defmethod handle-post :print [conn {:keys [content order]}]
|
222
|
+
(do (constrain-order order (fn [] (do (print (read-string content))
|
223
|
+
(.flush *out*))))
|
224
|
+
(send-and-close conn 200 "ignore__")))
|
225
|
+
|
226
|
+
(defmethod handle-post :result [conn {:keys [content order]}]
|
227
|
+
(constrain-order order (fn [] (do (return-value content)
|
228
|
+
(set-connection conn)))))
|
229
|
+
|
230
|
+
(defn handle-connection
|
231
|
+
[opts conn]
|
232
|
+
(let [rdr (BufferedReader. (InputStreamReader. (.getInputStream conn)))]
|
233
|
+
(if-let [request (read-request rdr)]
|
234
|
+
(case (:method request)
|
235
|
+
:get (handle-get opts conn request)
|
236
|
+
:post (handle-post conn (read-string (:content request)))
|
237
|
+
(.close conn))
|
238
|
+
(.close conn))))
|
239
|
+
|
240
|
+
(defn server-loop
|
241
|
+
[opts server-socket]
|
242
|
+
(let [conn (.accept server-socket)]
|
243
|
+
(do (.setKeepAlive conn true)
|
244
|
+
(future (handle-connection opts conn))
|
245
|
+
(recur opts server-socket))))
|
246
|
+
|
247
|
+
(defn start-server
|
248
|
+
"Start the server on the specified port."
|
249
|
+
[opts]
|
250
|
+
(do (println "Starting Server on Port:" (:port opts))
|
251
|
+
(let [ss (ServerSocket. (:port opts))]
|
252
|
+
(future (server-loop opts ss))
|
253
|
+
(swap! server-state (fn [old] (assoc old :socket ss :port (:port opts)))))))
|
254
|
+
|
255
|
+
(defn stop-server
|
256
|
+
[]
|
257
|
+
(.close (:socket @server-state)))
|
258
|
+
|
259
|
+
(defn browser-eval
|
260
|
+
"Given a string of JavaScript, evaluate it in the browser and return a map representing the
|
261
|
+
result of the evaluation. The map will contain the keys :type and :value. :type can be
|
262
|
+
:success, :exception, or :error. :success means that the JavaScript was evaluated without
|
263
|
+
exception and :value will contain the return value of the evaluation. :exception means that
|
264
|
+
there was an exception in the browser while evaluating the JavaScript and :value will
|
265
|
+
contain the error message. :error means that some other error has occured."
|
266
|
+
[form]
|
267
|
+
(let [return-value (promise)]
|
268
|
+
(send-for-eval form
|
269
|
+
(fn [val] (deliver return-value val)))
|
270
|
+
(let [ret @return-value]
|
271
|
+
(try (read-string ret)
|
272
|
+
(catch Exception e
|
273
|
+
{:status :error
|
274
|
+
:value (str "Could not read return value: " ret)})))))
|
275
|
+
|
276
|
+
(defn- object-query-str
|
277
|
+
"Given a list of goog namespaces, create a JavaScript string which, when evaluated,
|
278
|
+
will return true if all of the namespaces exist and false if any do not exist."
|
279
|
+
[ns]
|
280
|
+
(str "if("
|
281
|
+
(apply str (interpose " && " (map #(str "goog.getObjectByName('" (name %) "')") ns)))
|
282
|
+
"){true}else{false};"))
|
283
|
+
|
284
|
+
(defn load-javascript [repl-env ns url]
|
285
|
+
(let [missing (remove #(contains? @loaded-libs %) ns)]
|
286
|
+
(when (seq missing)
|
287
|
+
(let [ret (browser-eval (object-query-str ns))]
|
288
|
+
(when-not (and (= (:status ret) :success)
|
289
|
+
(= (:value ret) "true"))
|
290
|
+
(browser-eval (slurp url))))
|
291
|
+
(swap! loaded-libs (partial apply conj) missing))))
|
292
|
+
|
293
|
+
(extend-protocol repl/IJavaScriptEnv
|
294
|
+
clojure.lang.IPersistentMap
|
295
|
+
(-setup [this]
|
296
|
+
(comp/with-core-cljs (start-server this)))
|
297
|
+
(-evaluate [_ _ _ js] (browser-eval js))
|
298
|
+
(-load [this ns url] (load-javascript this ns url))
|
299
|
+
(-tear-down [_]
|
300
|
+
(do (stop-server)
|
301
|
+
(reset! server-state {}))))
|
302
|
+
|
303
|
+
(defn compile-client-js [opts]
|
304
|
+
(cljsc/build '[(ns clojure.browser.repl.client
|
305
|
+
(:require [goog.events :as event]
|
306
|
+
[clojure.browser.repl :as repl]))
|
307
|
+
(defn start [url]
|
308
|
+
(event/listen js/window
|
309
|
+
"load"
|
310
|
+
(fn []
|
311
|
+
(repl/start-evaluator url))))]
|
312
|
+
{:optimizations (:optimizations opts)
|
313
|
+
:output-dir (:working-dir opts)}))
|
314
|
+
|
315
|
+
(defn create-client-js-file [opts file-path]
|
316
|
+
(let [file (io/file file-path)]
|
317
|
+
(when (not (.exists file))
|
318
|
+
(spit file (compile-client-js opts)))
|
319
|
+
file))
|
320
|
+
|
321
|
+
(defn repl-env [& {:as opts}]
|
322
|
+
(let [opts (merge {:port 9000 :optimizations :simple :working-dir ".repl"} opts)]
|
323
|
+
(do (swap! server-state
|
324
|
+
(fn [old] (assoc old :client-js
|
325
|
+
(future (create-client-js-file
|
326
|
+
opts
|
327
|
+
(io/file (:working-dir opts) "client.js"))))))
|
328
|
+
opts)))
|
329
|
+
|
330
|
+
(comment
|
331
|
+
|
332
|
+
(require '[cljs.repl :as repl])
|
333
|
+
(require '[cljs.repl.browser :as browser])
|
334
|
+
(def env (browser/repl-env))
|
335
|
+
(repl/repl env)
|
336
|
+
;; simulate the browser with curl
|
337
|
+
;; curl -v -d "ready" http://127.0.0.1:9000
|
338
|
+
ClojureScript:> (+ 1 1)
|
339
|
+
;; curl -v -d "2" http://127.0.0.1:9000
|
340
|
+
|
341
|
+
)
|