clementine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +4 -0
  3. data/README.md +52 -0
  4. data/Rakefile +1 -0
  5. data/clementine.gemspec +23 -0
  6. data/lib/clementine.rb +27 -0
  7. data/lib/clementine/clementine_rails.rb +8 -0
  8. data/lib/clementine/clojurescript_engine.rb +49 -0
  9. data/lib/clementine/clojurescript_engine_mri.rb +65 -0
  10. data/lib/clementine/clojurescript_template.rb +21 -0
  11. data/lib/clementine/options.rb +9 -0
  12. data/lib/clementine/version.rb +3 -0
  13. data/test/clojurescript_engine_test.rb +46 -0
  14. data/test/options_test.rb +22 -0
  15. data/vendor/assets/bin/cljsc.clj +21 -0
  16. data/vendor/assets/lib/clojure.jar +0 -0
  17. data/vendor/assets/lib/compiler.jar +0 -0
  18. data/vendor/assets/lib/goog.jar +0 -0
  19. data/vendor/assets/lib/js.jar +0 -0
  20. data/vendor/assets/src/clj/cljs/closure.clj +823 -0
  21. data/vendor/assets/src/clj/cljs/compiler.clj +1341 -0
  22. data/vendor/assets/src/clj/cljs/core.clj +702 -0
  23. data/vendor/assets/src/clj/cljs/repl.clj +162 -0
  24. data/vendor/assets/src/clj/cljs/repl/browser.clj +341 -0
  25. data/vendor/assets/src/clj/cljs/repl/rhino.clj +170 -0
  26. data/vendor/assets/src/cljs/cljs/core.cljs +3330 -0
  27. data/vendor/assets/src/cljs/cljs/nodejs.cljs +11 -0
  28. data/vendor/assets/src/cljs/cljs/nodejs_externs.js +2 -0
  29. data/vendor/assets/src/cljs/cljs/nodejscli.cljs +9 -0
  30. data/vendor/assets/src/cljs/cljs/reader.cljs +360 -0
  31. data/vendor/assets/src/cljs/clojure/browser/dom.cljs +106 -0
  32. data/vendor/assets/src/cljs/clojure/browser/event.cljs +100 -0
  33. data/vendor/assets/src/cljs/clojure/browser/net.cljs +182 -0
  34. data/vendor/assets/src/cljs/clojure/browser/repl.cljs +109 -0
  35. data/vendor/assets/src/cljs/clojure/set.cljs +162 -0
  36. data/vendor/assets/src/cljs/clojure/string.cljs +160 -0
  37. data/vendor/assets/src/cljs/clojure/walk.cljs +94 -0
  38. data/vendor/assets/src/cljs/clojure/zip.cljs +291 -0
  39. 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
+ )