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.
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
+ )