clementine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
)
|