Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Support code reflection in the cljs repl.

A few changes have been made to support runtime code reflection in a
cljs repl. These include small changes to cljs.analyzer, a separation of
the server element of cljs.repl.browser into cljs.repl.server, and the
addition of two new namespaces: cljs.repl.reflect (in src/clj) and
clojure.reflect (in src/cljs).

cljs.analyzer:
- Arbitrary metadata declared on symbols will now be added to the AST.
  This supports the addition of docstrings.
- Fix a subtle bug in cljs.analyzer/analyze-file, where an uncommon
  code-path would lead to the failed coercion of an absolute-path into a
  URL. An absolute path, including a `file://` protocol, can now be
  passed into the function successfully.

cljs.repl:
- Add function to analyze source on repl-env -setup. This is used to
  support reflection on user-defined cljs source files, as well as to
  populate the cljs.analyzer/namespaces atom on repl startup.

cljs.repl.browser:
- The server element of this namespace has been factored out into
  cljs.repl.server to support other services that may require that
  functionality.

cljs.repl.server:
- Expose a simple HTTP method and predicate dispatch system to register
  handler functions for incoming requests. (Note: this system seems to
  be relatively brittle, and future change may be warranted.)

cljs.repl.reflect:
- Registers a server handler for incoming requests to "/reflect".
- Queries cljs.analyzer/namespaces for meta information relevant to a
  symbol, responding to requests with compiled javascript.
- Can use "fixed point" macroexpansion on cljs macro forms.

clojure.reflect:
- Expose a set of simple functions for querying meta information of a
  symbol, as well as macroexpanding a cljs form.
commit b5b20fdc4fa5c9f8a12e527407a72a7e6957bcd6 1 parent 0f73237
@zachallaun zachallaun authored David Nolen committed
View
1  .gitignore
@@ -9,3 +9,4 @@ closure
/coresimple.js
/out
.repl
+*.swp
View
3  samples/repl/src/repl/test.cljs
@@ -7,7 +7,8 @@
;; You must not remove this notice, or any other, from this software.
(ns repl.test
- (:require [clojure.browser.repl :as repl]))
+ (:require [clojure.browser.repl :as repl]
+ [clojure.reflect :as reflect]))
(repl/connect "http://localhost:9000/repl")
View
16 script/browser-repl
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+if [ "$CLOJURESCRIPT_HOME" = "" ]; then
+ CLOJURESCRIPT_HOME="`dirname $0`/.."
+fi
+
+CLJSC_CP=''
+for next in lib/*: src/clj: src/cljs: test/cljs; do
+ CLJSC_CP=$CLJSC_CP$CLOJURESCRIPT_HOME'/'$next
+done
+
+java -server -cp $CLJSC_CP clojure.main -e "
+(require '[cljs.repl :as r])
+(require '[cljs.repl.browser :as b])
+(r/repl (b/repl-env))
+"
View
11 src/clj/cljs/analyzer.clj
@@ -290,6 +290,7 @@
([_ sym doc init] {:sym sym :doc doc :init init}))
args (apply pfn form)
sym (:sym args)
+ sym-meta (meta sym)
tag (-> sym meta :tag)
protocol (-> sym meta :protocol)
dynamic (-> sym meta :dynamic)
@@ -326,6 +327,7 @@
(let [m (assoc (or m {}) :name name)]
(merge m
(when tag {:tag tag})
+ (when sym-meta sym-meta)
(when dynamic {:dynamic true})
(when-let [line (:line env)]
{:file *cljs-file* :line line})
@@ -533,7 +535,7 @@
(when (and known-num-fields (not= known-num-fields argc))
(warning env
(str "WARNING: Wrong number of args (" argc ") passed to " ctor)))
-
+
{:env env :op :new :form form :ctor ctorexpr :args argexprs
:children (into [ctorexpr] argexprs)})))
@@ -677,7 +679,7 @@
:type true
:num-fields (count fields))]
(merge m
- {:protocols (-> tsym meta :protocols)}
+ {:protocols (-> tsym meta :protocols)}
(when-let [line (:line env)]
{:file *cljs-file*
:line line})))))
@@ -935,8 +937,8 @@
:else {:op :constant :env env :form form}))))
(defn analyze-file
- [f]
- (let [res (if (= \/ (first f)) f (io/resource f))]
+ [^String f]
+ (let [res (if (re-find #"^file://" f) (java.net.URL. f) (io/resource f))]
(assert res (str "Can't find " f " in classpath"))
(binding [*cljs-ns* 'cljs.user
*cljs-file* (.getPath ^java.net.URL res)
@@ -950,4 +952,3 @@
(when-not (identical? eof r)
(analyze env r)
(recur (read pbr false eof false))))))))))
-
View
16 src/clj/cljs/repl.clj
@@ -8,6 +8,7 @@
(ns cljs.repl
(:refer-clojure :exclude [load-file])
+ (:import java.io.File)
(:require [clojure.string :as string]
[clojure.java.io :as io]
[cljs.compiler :as comp]
@@ -149,6 +150,15 @@
'clojure.core/load-file load-file-fn
'load-namespace (fn [repl-env ns] (load-namespace repl-env ns))}))
+(defn analyze-source
+ "Given a source directory, analyzes all .cljs files. Used to populate
+ cljs.analyzer/namespaces so as to support code reflection."
+ [src-dir]
+ (if-let [src-dir (and (not (empty? src-dir))
+ (File. src-dir))]
+ (doseq [file (comp/cljs-files-in src-dir)]
+ (ana/analyze-file (str "file://" (.getAbsolutePath file))))))
+
(defn repl
"Note - repl will reload core.cljs every time, even if supplied old repl-env"
[repl-env & {:keys [verbose warn-on-undeclared special-fns]}]
@@ -166,12 +176,12 @@
(let [{:keys [status form]} (read-next-form)]
(cond
(= form :cljs/quit) :quit
-
+
(= status :error) (recur)
-
+
(and (seq? form) (is-special-fn? (first form)))
(do (apply (get special-fns (first form)) repl-env (rest form)) (newline) (recur))
-
+
:else
(do (eval-and-print repl-env env form) (recur)))))
(-tear-down repl-env))))
View
234 src/clj/cljs/repl/browser.clj
@@ -8,168 +8,47 @@
(ns cljs.repl.browser
(:refer-clojure :exclude [loaded-libs])
- (:require [clojure.string :as str]
- [clojure.java.io :as io]
+ (:require [clojure.java.io :as io]
[cljs.compiler :as comp]
[cljs.closure :as cljsc]
- [cljs.repl :as repl])
- (:import java.io.BufferedReader
- java.io.BufferedWriter
- java.io.InputStreamReader
- java.io.OutputStreamWriter
- java.net.Socket
- java.net.ServerSocket
- cljs.repl.IJavaScriptEnv))
-
-(defonce server-state (atom {:socket nil
- :connection nil
- :promised-conn nil
- :return-value-fn nil
- :client-js nil}))
+ [cljs.repl :as repl]
+ [cljs.repl.server :as server])
+ (:import cljs.repl.IJavaScriptEnv))
+
+(defonce browser-state (atom {:return-value-fn nil
+ :client-js nil}))
(def loaded-libs (atom #{}))
(def preloaded-libs (atom #{}))
-(defn- connection
- "Promise to return a connection when one is available. If a
- connection is not available, store the promise in server-state."
- []
- (let [p (promise)
- conn (:connection @server-state)]
- (if (and conn (not (.isClosed conn)))
- (do (deliver p conn)
- p)
- (do (swap! server-state (fn [old] (assoc old :promised-conn p)))
- p))))
-
-(defn- set-connection
- "Given a new available connection, either use it to deliver the
- connection which was promised or store the connection for later
- use."
- [conn]
- (if-let [promised-conn (:promised-conn @server-state)]
- (do (swap! server-state (fn [old] (-> old
- (assoc :connection nil)
- (assoc :promised-conn nil))))
- (deliver promised-conn conn))
- (swap! server-state (fn [old] (assoc old :connection conn)))))
-
(defn- set-return-value-fn
"Save the return value function which will be called when the next
return value is received."
[f]
- (swap! server-state (fn [old] (assoc old :return-value-fn f))))
-
-(defn- status-line [status]
- (case status
- 200 "HTTP/1.1 200 OK"
- 404 "HTTP/1.1 404 Not Found"
- "HTTP/1.1 500 Error"))
-
-(defn send-and-close
- "Use the passed connection to send a form to the browser. Send a
- proper HTTP response."
- ([conn status form]
- (send-and-close conn status form "text/html"))
- ([conn status form content-type]
- (let [utf-8-form (.getBytes form "UTF-8")
- content-length (count utf-8-form)
- headers (map #(.getBytes (str % "\r\n"))
- [(status-line status)
- "Server: ClojureScript REPL"
- (str "Content-Type: "
- content-type
- "; charset=utf-8")
- (str "Content-Length: " content-length)
- ""])]
- (with-open [os (.getOutputStream conn)]
- (do (doseq [header headers]
- (.write os header 0 (count header)))
- (.write os utf-8-form 0 content-length)
- (.flush os)
- (.close conn))))))
-
-(defn send-404 [conn path]
- (send-and-close conn 404
- (str "<html><body>"
- "<h2>Page not found</h2>"
- "No page " path " found on this server."
- "</body></html>")
- "text/html"))
+ (swap! browser-state (fn [old] (assoc old :return-value-fn f))))
(defn send-for-eval
"Given a form and a return value function, send the form to the
browser for evaluation. The return value function will be called
when the return value is received."
([form return-value-fn]
- (send-for-eval @(connection) form return-value-fn))
+ (send-for-eval @(server/connection) form return-value-fn))
([conn form return-value-fn]
(do (set-return-value-fn return-value-fn)
- (send-and-close conn 200 form "text/javascript"))))
+ (server/send-and-close conn 200 form "text/javascript"))))
(defn- return-value
"Called by the server when a return value is received."
[val]
- (when-let [f (:return-value-fn @server-state)]
+ (when-let [f (:return-value-fn @browser-state)]
(f val)))
-(defn parse-headers
- "Parse the headers of an HTTP POST request."
- [header-lines]
- (apply hash-map
- (mapcat
- (fn [line]
- (let [[k v] (str/split line #":" 2)]
- [(keyword (str/lower-case k)) (str/triml v)]))
- header-lines)))
-
-(comment
-
- (parse-headers
- ["Host: www.mysite.com"
- "User-Agent: Mozilla/4.0"
- "Content-Length: 27"
- "Content-Type: application/x-www-form-urlencoded"])
-)
-
-;;; assumes first line already consumed
-(defn read-headers [rdr]
- (loop [next-line (.readLine rdr)
- header-lines []]
- (if (= "" next-line)
- header-lines ;we're done reading headers
- (recur (.readLine rdr) (conj header-lines next-line)))))
-
-(defn read-post [line rdr]
- (let [[_ path _] (str/split line #" ")
- headers (parse-headers (read-headers rdr))
- content-length (Integer/parseInt (:content-length headers))
- content (char-array content-length)]
- (io! (.read rdr content 0 content-length)
- {:method :post
- :path path
- :headers headers
- :content (String. content)})))
-
-(defn read-get [line rdr]
- (let [[_ path _] (str/split line #" ")
- headers (parse-headers (read-headers rdr))]
- {:method :get
- :path path
- :headers headers}))
-
-(defn read-request [rdr]
- (let [line (.readLine rdr)]
- (cond (.startsWith line "POST") (read-post line rdr)
- (.startsWith line "GET") (read-get line rdr)
- :else {:method :unknown :content line})))
-
(defn repl-client-js []
- (slurp @(:client-js @server-state)))
+ (slurp @(:client-js @browser-state)))
(defn send-repl-client-page
- [opts conn request]
- (send-and-close conn 200
+ [request conn opts]
+ (server/send-and-close conn 200
(str "<html><head><meta charset=\"UTF-8\"></head><body>
<script type=\"text/javascript\">"
(repl-client-js)
@@ -180,7 +59,7 @@
"</body></html>")
"text/html"))
-(defn send-static [opts conn {path :path :as request}]
+(defn send-static [{path :path :as request} conn opts]
(if (and (:static-dir opts)
(not= "/favicon.ico" path))
(let [path (if (= "/" path) "/index.html" path)
@@ -188,28 +67,31 @@
(if-let [local-path (seq (for [x (if (string? st-dir) [st-dir] st-dir)
:when (.exists (io/file (str x path)))]
(str x path)))]
- (send-and-close conn 200 (slurp (first local-path))
+ (server/send-and-close conn 200 (slurp (first local-path))
(condp #(.endsWith %2 %1) path
".js" "text/javascript"
".html" "text/html"
"text/plain"))
- (send-404 conn path)))
- (send-404 conn path)))
+ (server/send-404 conn path)))
+ (server/send-404 conn path)))
-(defn handle-get [opts conn request]
- (let [path (:path request)]
- (cond
- (.startsWith path "/repl") (send-repl-client-page opts conn request)
- (:serve-static opts) (send-static opts conn request)
- :else (send-404 conn (:path request)))))
+(server/dispatch-on :get
+ (fn [{:keys [path]} _ _] (.startsWith path "/repl"))
+ send-repl-client-page)
-(declare browser-eval)
+(server/dispatch-on :get
+ (fn [{:keys [path]} _ _] (or (= path "/")
+ (.endsWith path ".js")
+ (.endsWith path ".html")))
+ send-static)
-(def ordering (agent {:expecting nil :fns {}}))
+(defmulti handle-post (fn [m _ _ ] (:type m)))
+
+(server/dispatch-on :post (constantly true) handle-post)
-(defmulti handle-post (fn [_ m] (:type m)))
+(def ordering (agent {:expecting nil :fns {}}))
-(defmethod handle-post :ready [conn _]
+(defmethod handle-post :ready [_ conn _]
(do (reset! loaded-libs @preloaded-libs)
(send ordering (fn [_] {:expecting nil :fns {}}))
(send-for-eval conn
@@ -236,42 +118,14 @@
(send-off ordering add-in-order order f)
(send-off ordering run-in-order))
-(defmethod handle-post :print [conn {:keys [content order]}]
+(defmethod handle-post :print [{:keys [content order]} conn _ ]
(do (constrain-order order (fn [] (do (print (read-string content))
(.flush *out*))))
- (send-and-close conn 200 "ignore__")))
+ (server/send-and-close conn 200 "ignore__")))
-(defmethod handle-post :result [conn {:keys [content order]}]
+(defmethod handle-post :result [{:keys [content order]} conn _ ]
(constrain-order order (fn [] (do (return-value content)
- (set-connection conn)))))
-
-(defn handle-connection
- [opts conn]
- (let [rdr (BufferedReader. (InputStreamReader. (.getInputStream conn)))]
- (if-let [request (read-request rdr)]
- (case (:method request)
- :get (handle-get opts conn request)
- :post (handle-post conn (read-string (:content request)))
- (.close conn))
- (.close conn))))
-
-(defn server-loop
- [opts server-socket]
- (let [conn (.accept server-socket)]
- (do (.setKeepAlive conn true)
- (future (handle-connection opts conn))
- (recur opts server-socket))))
-
-(defn start-server
- "Start the server on the specified port."
- [opts]
- (let [ss (ServerSocket. (:port opts))]
- (future (server-loop opts ss))
- (swap! server-state (fn [old] (assoc old :socket ss :port (:port opts))))))
-
-(defn stop-server
- []
- (.close (:socket @server-state)))
+ (server/set-connection conn)))))
(defn browser-eval
"Given a string of JavaScript, evaluate it in the browser and return a map representing the
@@ -305,12 +159,15 @@
(extend-protocol repl/IJavaScriptEnv
clojure.lang.IPersistentMap
(-setup [this]
- (comp/with-core-cljs (start-server this)))
+ (do (require 'cljs.repl.reflect)
+ (repl/analyze-source (:src this))
+ (comp/with-core-cljs (server/start this))))
(-evaluate [_ _ _ js] (browser-eval js))
(-load [this ns url] (load-javascript this ns url))
(-tear-down [_]
- (do (stop-server)
- (reset! server-state {}))))
+ (do (server/stop)
+ (reset! server/state {})
+ (reset! browser-state {}))))
(defn compile-client-js [opts]
(cljsc/build '[(ns clojure.browser.repl.client
@@ -361,6 +218,8 @@
loading code and reloading it would cause a problem.
optimizations: The level of optimization to use when compiling the client
end of the REPL. Defaults to :simple.
+ src: The source directory containing user-defined cljs files. Used to
+ support reflection. Defaults to \"src/\".
"
[& {:as opts}]
(let [opts (merge {:port 9000
@@ -368,11 +227,12 @@
:working-dir ".repl"
:serve-static true
:static-dir ["." "out/"]
- :preloaded-libs []}
+ :preloaded-libs []
+ :src "src/"}
opts)]
(do (reset! preloaded-libs (set (concat (always-preload) (map str (:preloaded-libs opts)))))
(reset! loaded-libs @preloaded-libs)
- (swap! server-state
+ (swap! browser-state
(fn [old] (assoc old :client-js
(future (create-client-js-file
opts
@@ -380,7 +240,7 @@
opts)))
(comment
-
+
(require '[cljs.repl :as repl])
(require '[cljs.repl.browser :as browser])
(def env (browser/repl-env))
View
74 src/clj/cljs/repl/reflect.clj
@@ -0,0 +1,74 @@
+(ns cljs.repl.reflect
+ (:refer-clojure :exclude [macroexpand])
+ (:require [cljs.repl.server :as server]
+ [cljs.analyzer :as analyzer]
+ [cljs.compiler :as compiler]
+ [clojure.string :as str]))
+
+(defn- dissoc-unless
+ "Dissoc all keys from map that do not appear in key-set.
+
+ (dissoc-unless {:foo 1 :bar 2} #{:foo})
+ => {:foo 1}"
+ [m key-set]
+ {:pre [(map? m)
+ (set? key-set)]}
+ (reduce (fn [coll key]
+ (if (contains? key-set key)
+ coll
+ (dissoc coll key)))
+ m (keys m)))
+
+(defn- get-meta [sym]
+ (let [ns (symbol (namespace sym))
+ n (symbol (name sym))]
+ (if-let [sym-meta (get (:defs (get @analyzer/namespaces ns)) n)]
+ (-> (dissoc-unless sym-meta
+ #{:name :method-params :doc :line :file})
+ (update-in [:name] str)
+ (update-in [:method-params] #(str (vec %)))))))
+
+(defn macroexpand [form]
+ "Fully expands a cljs macro form."
+ (let [mform (analyzer/macroexpand-1 {} form)]
+ (if (identical? form mform)
+ mform
+ (macroexpand mform))))
+
+(defn- url-decode [encoded & [encoding]]
+ (java.net.URLDecoder/decode encoded (or encoding "UTF-8")))
+
+(def read-url-string (comp read-string url-decode))
+
+(defn parse-param
+ "Parses the query parameter of a path of the form \"/reflect?var=foo\"
+ into the vector [\"var\" \"foo\"]."
+ [path]
+ (-> (str/split path #"\?")
+ (last)
+ (str/split #"=")))
+
+(defn- compile-and-return
+ "Compiles a form to javascript and returns it on conn."
+ [conn form]
+ (let [ast (analyzer/analyze {:ns {:name 'cljs.user}} form)
+ js (try (compiler/emit-str ast)
+ (catch Exception e (println e)))]
+ (server/send-and-close conn 200 js "text/javascript")))
+
+(defmulti handle-reflect-query (fn [[param _] & _] param))
+
+(defmethod handle-reflect-query "var"
+ [[_ sym] req conn opts]
+ (let [sym (read-url-string sym)]
+ (compile-and-return conn (get-meta sym))))
+
+(defmethod handle-reflect-query "macroform"
+ [[_ mform] req conn opts]
+ (let [mform (-> mform read-url-string macroexpand)]
+ (server/send-and-close conn 200 (str mform))))
+
+(server/dispatch-on :get
+ (fn [{:keys [path]} _ _] (.startsWith path "/reflect"))
+ (fn [{:keys [path] :as req} conn opts]
+ (handle-reflect-query (parse-param path) req conn opts)))
View
173 src/clj/cljs/repl/server.clj
@@ -0,0 +1,173 @@
+(ns cljs.repl.server
+ (:refer-clojure :exclude [loaded-libs])
+ (:require [clojure.string :as str]
+ [clojure.java.io :as io]
+ [cljs.compiler :as comp]
+ [cljs.closure :as cljsc]
+ [cljs.repl :as repl])
+ (:import java.io.BufferedReader
+ java.io.BufferedWriter
+ java.io.InputStreamReader
+ java.io.OutputStreamWriter
+ java.net.Socket
+ java.net.ServerSocket
+ cljs.repl.IJavaScriptEnv))
+
+(defonce state (atom {:socket nil
+ :connection nil
+ :promised-conn nil}))
+
+(defn connection
+ "Promise to return a connection when one is available. If a
+ connection is not available, store the promise in server/state."
+ []
+ (let [p (promise)
+ conn (:connection @state)]
+ (if (and conn (not (.isClosed conn)))
+ (do (deliver p conn)
+ p)
+ (do (swap! state (fn [old] (assoc old :promised-conn p)))
+ p))))
+
+(defn set-connection
+ "Given a new available connection, either use it to deliver the
+ connection which was promised or store the connection for later
+ use."
+ [conn]
+ (if-let [promised-conn (:promised-conn @state)]
+ (do (swap! state (fn [old] (-> old
+ (assoc :connection nil)
+ (assoc :promised-conn nil))))
+ (deliver promised-conn conn))
+ (swap! state (fn [old] (assoc old :connection conn)))))
+
+(defonce handlers (atom {}))
+
+(defn dispatch-on
+ "Registers a handler to be dispatched based on a request method and a
+ predicate.
+
+ pred should be a function that accepts an options map, a connection,
+ and a request map and returns a boolean value based on whether or not
+ that request should be dispatched to the related handler."
+ ([method pred handler]
+ (dispatch-on method {:pred pred :handler handler}))
+ ([method {:as m}]
+ (swap! handlers (fn [old]
+ (update-in old [method] #(conj (vec %) m))))))
+
+;;; assumes first line already consumed
+(defn parse-headers
+ "Parse the headers of an HTTP POST request."
+ [header-lines]
+ (apply hash-map
+ (mapcat
+ (fn [line]
+ (let [[k v] (str/split line #":" 2)]
+ [(keyword (str/lower-case k)) (str/triml v)]))
+ header-lines)))
+
+(defn read-headers [rdr]
+ (loop [next-line (.readLine rdr)
+ header-lines []]
+ (if (= "" next-line)
+ header-lines ;we're done reading headers
+ (recur (.readLine rdr) (conj header-lines next-line)))))
+
+(defn read-post [line rdr]
+ (let [[_ path _] (str/split line #" ")
+ headers (parse-headers (read-headers rdr))
+ content-length (Integer/parseInt (:content-length headers))
+ content (char-array content-length)]
+ (io! (.read rdr content 0 content-length)
+ {:method :post
+ :path path
+ :headers headers
+ :content (String. content)})))
+
+(defn read-get [line rdr]
+ (let [[_ path _] (str/split line #" ")
+ headers (parse-headers (read-headers rdr))]
+ {:method :get
+ :path path
+ :headers headers}))
+
+(defn read-request [rdr]
+ (let [line (.readLine rdr)]
+ (cond (.startsWith line "POST") (read-post line rdr)
+ (.startsWith line "GET") (read-get line rdr)
+ :else {:method :unknown :content line})))
+
+(defn- status-line [status]
+ (case status
+ 200 "HTTP/1.1 200 OK"
+ 404 "HTTP/1.1 404 Not Found"
+ "HTTP/1.1 500 Error"))
+
+(defn send-and-close
+ "Use the passed connection to send a form to the browser. Send a
+ proper HTTP response."
+ ([conn status form]
+ (send-and-close conn status form "text/html"))
+ ([conn status form content-type]
+ (let [utf-8-form (.getBytes form "UTF-8")
+ content-length (count utf-8-form)
+ headers (map #(.getBytes (str % "\r\n"))
+ [(status-line status)
+ "Server: ClojureScript REPL"
+ (str "Content-Type: "
+ content-type
+ "; charset=utf-8")
+ (str "Content-Length: " content-length)
+ ""])]
+ (with-open [os (.getOutputStream conn)]
+ (do (doseq [header headers]
+ (.write os header 0 (count header)))
+ (.write os utf-8-form 0 content-length)
+ (.flush os)
+ (.close conn))))))
+
+(defn send-404 [conn path]
+ (send-and-close conn 404
+ (str "<html><body>"
+ "<h2>Page not found</h2>"
+ "No page " path " found on this server."
+ "</body></html>")
+ "text/html"))
+
+(defn- dispatch-request [request conn opts]
+ (if-let [handlers ((:method request) @handlers)]
+ (if-let [handler (some (fn [{:keys [pred handler]}]
+ (when (pred request conn opts)
+ handler))
+ handlers)]
+ (if (= :post (:method request))
+ (handler (read-string (:content request)) conn opts )
+ (handler request conn opts))
+ (send-404 conn (:path request)))
+ (.close conn)))
+
+(defn- handle-connection
+ [opts conn]
+ (let [rdr (BufferedReader. (InputStreamReader. (.getInputStream conn)))]
+ (if-let [request (read-request rdr)]
+ (dispatch-request request conn opts)
+ (.close conn))))
+
+(defn- server-loop
+ [opts server-socket]
+ (let [conn (.accept server-socket)]
+ (do (.setKeepAlive conn true)
+ (future (handle-connection opts conn))
+ (recur opts server-socket))))
+
+(defn start
+ "Start the server on the specified port."
+ [opts]
+ (let [ss (ServerSocket. (:port opts))]
+ (future (server-loop opts ss))
+ (swap! state (fn [old] (assoc old :socket ss :port (:port opts))))))
+
+(defn stop
+ []
+ (.close (:socket @state)))
View
45 src/cljs/clojure/reflect.cljs
@@ -0,0 +1,45 @@
+(ns clojure.reflect
+ (:require [clojure.browser.net :as net]
+ [clojure.browser.event :as event]))
+
+(defn- evaluate-javascript [block]
+ (let [result (try (js* "eval(~{block})")
+ (catch js/Error e
+ (.log js/console e)))]
+ result))
+
+(defn- query-reflection
+ "Issues a GET to /reflect with a single query-parameter string.
+ Calls cb with the result."
+ [query-param cb]
+ (let [conn (net/xhr-connection)
+ url (str "/reflect?" query-param)]
+ (event/listen conn :success (fn [e]
+ (let [resp (.getResponseText e/currentTarget ())]
+ (cb resp))))
+ (event/listen conn :error #(println "Reflection query failed."))
+ (net/transmit conn url)))
+
+(defn query-meta
+ "Queries the reflection api with a fully qualified symbol, then calls
+ callback fn cb with the evaluated cljs map containing that symbol's
+ meta information."
+ [sym cb]
+ (query-reflection (str "var=" (js/encodeURIComponent (str sym)))
+ #(cb (evaluate-javascript %))))
+
+(defn query-macroexpand
+ "Queries the reflection api with a quoted macro form, then calls the
+ callback function with the macroexpanded form, as a string."
+ [form]
+ (query-reflection (str "macroform=" (js/encodeURIComponent (str form))) println))
+
+(defn print-doc [{:keys [name method-params doc]}]
+ (when-not (empty? name)
+ (println name)
+ (println method-params)
+ (println doc)))
+
+(defn query-doc [sym]
+ (query-meta sym print-doc))
+
Please sign in to comment.
Something went wrong with that request. Please try again.