Skip to content
Browse files

split handlers namespace into separate topical middleware namespaces

  • Loading branch information...
1 parent 2b2b735 commit 4e3317c5ab032d0e08bc359966db64be702d2752 @cemerick cemerick committed Feb 14, 2012
View
4 README.md
@@ -247,11 +247,11 @@ value. For example, nREPL's default handler is constructed like so in the
```clojure
(defn default-handler
- "A default handler supporting interruptable evaluation, stdin, sessions, and
+ "A default handler supporting interruptible evaluation, stdin, sessions, and
readable representations of evaluated expressions via `pr`."
[]
(-> (constantly false)
- clojure.tools.nrepl.middleware.interruptable-eval/interruptible-eval
+ clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval
clojure.tools.nrepl.middleware.pr-values/pr-values
clojure.tools.nrepl.middleware.session/add-stdin
clojure.tools.nrepl.middleware.session/session))
View
253 src/main/clojure/clojure/tools/nrepl/handlers.clj
@@ -1,253 +0,0 @@
-(ns clojure.tools.nrepl.handlers
- (:use [clojure.tools.nrepl.misc :only (uuid response-for returning log)])
- (:require (clojure main test)
- [clojure.tools.nrepl.transport :as transport])
- (:import clojure.tools.nrepl.transport.Transport
- (java.io PipedReader PipedWriter Reader Writer PrintWriter StringReader)
- clojure.lang.LineNumberingPushbackReader))
-
-(defn evaluate
- "Evaluates some code within the dynamic context defined by `bindings`.
-
- Uses `clojure.main/repl` to drive the evaluation of :code in a second
- map argument, which may also optionally specify a :ns (which is resolved
- via `find-ns`). The map MUST contain a Transport implementation
- in :transport; expression results and errors will be sent to that
- transport implementation.
-
- Returns the dynamic scope that resulted after evaluating all expressions
- in :code, as per `clojure.core/get-thread-bindings`.
-
- It is assumed that `bindings` will contain useful/appropriate entries
- for all vars indicated by `clojure.main/with-bindings`."
- [bindings {:keys [code ns transport] :as msg}]
- (let [bindings (atom (merge bindings (when ns {#'*ns* (-> ns symbol find-ns)})))]
- (try
- (clojure.main/repl
- :init (fn [] (push-thread-bindings @bindings))
- :read (if (string? code)
- (let [reader (LineNumberingPushbackReader. (StringReader. code))]
- #(read reader false %2))
- (let [q (java.util.concurrent.ArrayBlockingQueue. (count code) false code)]
- #(or (.poll q 0 java.util.concurrent.TimeUnit/MILLISECONDS) %2)))
- :prompt (fn [])
- :need-prompt (constantly false)
- ; TODO pretty-print?
- :print (fn [v]
- (reset! bindings (-> (get-thread-bindings)
- (assoc #'*3 *2
- #'*2 *1
- #'*1 v)
- (dissoc #'*agent*)))
- (transport/send transport (response-for msg
- {:value v
- :ns (-> *ns* ns-name str)})))
- ; TODO customizable exception prints
- :caught (fn [e]
- (when-not (instance? ThreadDeath (#'clojure.main/root-cause e))
- (reset! bindings (-> (get-thread-bindings)
- (assoc #'*e e)
- (dissoc #'*agent*)))
- (transport/send transport (response-for msg {:status :eval-error}))
- (clojure.main/repl-caught e))))
- @bindings
- (finally
- (pop-thread-bindings)
- (.flush ^Writer (@bindings #'*out*))
- (.flush ^Writer (@bindings #'*err*))))))
-
-#_(defn- pool-size [] (.getPoolSize clojure.lang.Agent/soloExecutor))
-
-(defn unknown-op
- "A handler that always sends an {:unknown-op :status :op op} response."
- [{:keys [op transport] :as msg}]
- (transport/send transport (response-for msg {:status #{:error :unknown-op} :op op})))
-
-(def ^{:private true
- :dynamic true
- :doc "The message currently being evaluated."}
- *msg* nil)
-
-(defn- session-out
- [channel-type session-id transport]
- (let [buf (clojure.tools.nrepl.StdOutBuffer.)]
- (PrintWriter. (proxy [Writer] []
- (close [] (.flush ^Writer this))
- (write [& [x off len]]
- (locking buf
- (cond
- (number? x) (.append buf (char x))
- (not off) #(.append buf x)
- (instance? CharSequence x) (.append buf ^CharSequence x off len)
- :else (.append buf ^chars x off len))))
- (flush []
- (let [text (locking buf (let [text (str buf)]
- (.setLength buf 0)
- text))]
- (when (pos? (count text))
- (transport/send transport
- (response-for *msg* {:session session-id
- channel-type text})))))))))
-
-(defn- session-in
- [session-id transport]
- (let [request-input (fn [^PipedReader r]
- (when-not (.ready r)
- (transport/send transport
- (response-for *msg* {:session session-id
- :status :need-input}))))
- writer (PipedWriter.)
- reader (LineNumberingPushbackReader.
- (proxy [PipedReader] [writer]
- (close [])
- (read
- ([] (request-input this)
- (let [^Reader this this] (proxy-super read)))
- ([x] (request-input this)
- (let [^Reader this this]
- (if (instance? java.nio.CharBuffer x)
- (proxy-super read ^java.nio.CharBuffer x)
- (proxy-super read ^chars x))))
- ([buf off len]
- (let [^Reader this this]
- (request-input this)
- (proxy-super read buf off len))))))]
- [reader writer]))
-
-(def ^:private sessions (atom {}))
-
-(defn- session-error-handler
- [session-agent ex]
- #_(when-not (or (instance? InterruptedException ex)
- (instance? ThreadDeath ex))
- )
- (log ex "Session error, id " (-> session-agent meta :id)))
-
-(defn- create-session
- ([transport] (create-session transport {}))
- ([transport baseline-bindings]
- (clojure.main/with-bindings
- (let [id (uuid)
- out (session-out :out id transport)
- [in in-writer] (session-in id transport)]
- (binding [*out* out
- *err* (session-out :err id transport)
- *in* in
- *ns* (create-ns 'user)
- ; clojure.test captures *out* at load-time, so we need to make sure
- ; runtime output of test status/results is redirected properly
- ; TODO is this something we need to consider in general, or is this
- ; specific hack reasonable?
- clojure.test/*test-out* out]
- (agent (merge baseline-bindings (get-thread-bindings))
- :meta {:id id
- :stdin-writer in-writer}
- :error-mode :continue
- :error-handler #'session-error-handler))))))
-
-(defn- register-session
- [{:keys [session transport] :as msg}]
- (let [session (create-session transport @session)
- id (-> session meta :id)]
- (swap! sessions assoc id session)
- (transport/send transport (response-for msg {:status :done :new-session id}))))
-
-(defn- close-session
- [{:keys [session transport] :as msg}]
- (swap! sessions dissoc (-> session meta :id))
- (transport/send transport (response-for msg {:status #{:done :session-closed}})))
-
-(defn session
- [h]
- (fn [{:keys [op session transport] :as msg}]
- (let [the-session (if session
- (@sessions session)
- (create-session transport))]
- (if-not the-session
- (transport/send transport (response-for msg {:status #{:error :unknown-session}}))
- (let [msg (assoc msg :session the-session)]
- (case op
- "clone" (register-session msg)
- "close" (close-session msg)
- "ls-sessions" (transport/send transport (response-for msg {:status :done
- :sessions (keys @sessions)}))
- (h msg)))))))
-
-(defn interruptable-eval
- [h]
- (fn [{:keys [op session interrupt-id id transport] :as msg}]
- (case op
- "eval"
- (if-not (:code msg)
- (transport/send transport (response-for msg {:status #{:error :no-code}}))
- (send-off session
- (fn [bindings]
- (alter-meta! session assoc
- :thread (Thread/currentThread)
- :eval-msg msg)
- (binding [*msg* msg]
- (returning (dissoc (evaluate bindings msg) #'*msg*)
- (transport/send transport (response-for msg {:status :done}))
- (alter-meta! session dissoc :thread :eval-msg))))))
-
- "interrupt"
- ; interrupts are inherently racy; we'll check the agent's :eval-msg's :id and
- ; bail if it's different than the one provided, but it's possible for
- ; that message's eval to finish and another to start before we send
- ; the interrupt / .stop.
- (let [{:keys [id eval-msg ^Thread thread]} (meta session)]
- (if (or (not interrupt-id)
- (= interrupt-id (:id eval-msg)))
- (if-not thread
- (transport/send transport (response-for msg {:status #{:done :session-idle}}))
- (do
- (transport/send transport {:status #{:interrupted}
- :id (:id eval-msg)
- :session id})
- (.stop thread)
- (transport/send transport (response-for msg {:status #{:done}}))))
- (transport/send transport (response-for msg {:status #{:error :interrupt-id-mismatch :done}}))))
-
- (h msg))))
-
-(defn add-stdin
- "Writes content to a session-local Writer instance held in its metadata,
- to be picked up by the reader returned by transport-in."
- [h]
- (fn [{:keys [op stdin session transport] :as msg}]
- (if (= op "stdin")
- (do
- (-> session meta ^Writer (:stdin-writer) (.write stdin))
- (transport/send transport (response-for msg {:status :done})))
- (h msg))))
-
-(defn output-subscriptions
- [h]
- (fn [{:keys [op sub unsub] :as msg}]
- (case op
- "sub" ;; TODO
- "unsub"
- (h msg))))
-
-(defn prn-values
- [h]
- (fn [{:keys [op ^Transport transport] :as msg}]
- (if (not= op "eval")
- (h msg)
- (h (assoc msg :transport (reify Transport
- (recv [this] (.recv transport))
- (recv [this timeout] (.recv transport timeout))
- (send [this resp]
- (.send transport
- (if-let [[_ v] (find resp :value)]
- (assoc resp :value (with-out-str (pr v)))
- resp)))))))))
-
-(defn default-handler
- []
- (-> unknown-op
- interruptable-eval
- prn-values
- add-stdin
- ; output-subscriptions TODO
- session))
View
107 src/main/clojure/clojure/tools/nrepl/middleware/interruptible_eval.clj
@@ -0,0 +1,107 @@
+
+(ns ^{:author "Chas Emerick"}
+ clojure.tools.nrepl.middleware.interruptible-eval
+ (:require [clojure.tools.nrepl.transport :as t]
+ clojure.main)
+ (:use [clojure.tools.nrepl.misc :only (response-for returning)])
+ (:import clojure.lang.LineNumberingPushbackReader
+ (java.io StringReader Writer)
+ (java.util.concurrent ArrayBlockingQueue TimeUnit)))
+
+(def ^{:dynamic true
+ :doc "The message currently being evaluated."}
+ *msg* nil)
+
+(defn evaluate
+ "Evaluates some code within the dynamic context defined by a map of `bindings`,
+ as per `clojure.core/get-thread-bindings`.
+
+ Uses `clojure.main/repl` to drive the evaluation of :code in a second
+ map argument (either a string or a seq of forms to be evaluated), which may
+ also optionally specify a :ns (resolved via `find-ns`). The map MUST
+ contain a Transport implementation in :transport; expression results and errors
+ will be sent via that Transport.
+
+ Returns the dynamic scope that remains after evaluating all expressions
+ in :code.
+
+ It is assumed that `bindings` already contains useful/appropriate entries
+ for all vars indicated by `clojure.main/with-bindings`."
+ [bindings {:keys [code ns transport] :as msg}]
+ (let [bindings (atom (merge bindings (when ns {#'*ns* (-> ns symbol find-ns)})))]
+ (try
+ (clojure.main/repl
+ :init (fn [] (push-thread-bindings @bindings))
+ :read (if (string? code)
+ (let [reader (LineNumberingPushbackReader. (StringReader. code))]
+ #(read reader false %2))
+ (let [q (java.util.concurrent.ArrayBlockingQueue. (count code) false code)]
+ #(or (.poll q 0 java.util.concurrent.TimeUnit/MILLISECONDS) %2)))
+ :prompt (fn [])
+ :need-prompt (constantly false)
+ ; TODO pretty-print?
+ :print (fn [v]
+ (reset! bindings (assoc (get-thread-bindings)
+ #'*3 *2
+ #'*2 *1
+ #'*1 v))
+ (t/send transport (response-for msg
+ {:value v
+ :ns (-> *ns* ns-name str)})))
+ ; TODO customizable exception prints
+ :caught (fn [e]
+ (let [root-ex (#'clojure.main/root-cause e)]
+ (when-not (instance? ThreadDeath root-ex)
+ (reset! bindings (assoc (get-thread-bindings) #'*e e))
+ (t/send transport (response-for msg {:status :eval-error
+ :ex (-> e class str)
+ :root-ex (-> root-ex class str)}))
+ (clojure.main/repl-caught e)))))
+ @bindings
+ (finally
+ (pop-thread-bindings)
+ (.flush ^Writer (@bindings #'*out*))
+ (.flush ^Writer (@bindings #'*err*))))))
+
+#_(defn- pool-size [] (.getPoolSize clojure.lang.Agent/soloExecutor))
+
+
+(defn interruptible-eval
+ "Evaluation middleware that supports interrupts. Returns a handler that supports
+ \"eval\" and \"interrupt\" :op-erations that delegates to the given handler
+ otherwise."
+ [h]
+ (fn [{:keys [op session interrupt-id id transport] :as msg}]
+ (case op
+ "eval"
+ (if-not (:code msg)
+ (t/send transport (response-for msg :status #{:error :no-code}))
+ (send-off session
+ (fn [bindings]
+ (alter-meta! session assoc
+ :thread (Thread/currentThread)
+ :eval-msg msg)
+ (binding [*msg* msg]
+ (returning (dissoc (evaluate bindings msg) #'*msg* #'*agent*)
+ (t/send transport (response-for msg :status :done))
+ (alter-meta! session dissoc :thread :eval-msg))))))
+
+ "interrupt"
+ ; interrupts are inherently racy; we'll check the agent's :eval-msg's :id and
+ ; bail if it's different than the one provided, but it's possible for
+ ; that message's eval to finish and another to start before we send
+ ; the interrupt / .stop.
+ (let [{:keys [id eval-msg ^Thread thread]} (meta session)]
+ (if (or (not interrupt-id)
+ (= interrupt-id (:id eval-msg)))
+ (if-not thread
+ (t/send transport (response-for msg :status #{:done :session-idle}))
+ (do
+ (.stop thread)
+ (t/send transport {:status #{:interrupted}
+ :id (:id eval-msg)
+ :session id})
+ (t/send transport (response-for msg :status #{:done}))))
+ (t/send transport (response-for msg :status #{:error :interrupt-id-mismatch :done}))))
+
+ (h msg))))
View
25 src/main/clojure/clojure/tools/nrepl/middleware/pr_values.clj
@@ -0,0 +1,25 @@
+
+(ns ^{:author "Chas Emerick"}
+ clojure.tools.nrepl.middleware.pr-values
+ (:require [clojure.tools.nrepl.transport :as t])
+ (:import clojure.tools.nrepl.transport.Transport))
+
+(defn pr-values
+ "Middleware that returns a handler which transforms any :value slots
+ in messages sent via the request's Transport to strings via `pr`,
+ delegating all actual message handling to the provided handler.
+
+ Requires that results of eval operations are sent in messages in a
+ :value slot."
+ [h]
+ (fn [{:keys [op ^Transport transport] :as msg}]
+ (if (not= op "eval")
+ (h msg)
+ (h (assoc msg :transport (reify Transport
+ (recv [this] (.recv transport))
+ (recv [this timeout] (.recv transport timeout))
+ (send [this resp]
+ (.send transport
+ (if-let [[_ v] (find resp :value)]
+ (assoc resp :value (with-out-str (pr v)))
+ resp)))))))))
View
173 src/main/clojure/clojure/tools/nrepl/middleware/session.clj
@@ -0,0 +1,173 @@
+
+(ns ^{:doc "Support for persistent, cross-connection REPL sessions."
+ :author "Chas Emerick"}
+ clojure.tools.nrepl.middleware.session
+ (:use [clojure.tools.nrepl.misc :only (uuid response-for returning log)]
+ [clojure.tools.nrepl.middleware.interruptible-eval :only (*msg*)])
+ (:require (clojure main test)
+ [clojure.tools.nrepl.transport :as t])
+ (:import clojure.tools.nrepl.transport.Transport
+ (java.io PipedReader PipedWriter Reader Writer PrintWriter StringReader)
+ clojure.lang.LineNumberingPushbackReader))
+
+(def ^{:private true} sessions (atom {}))
+
+;; TODO the way this is currently, :out and :err will continue to be
+;; associated with a particular *msg* (and session) even when produced from a future,
+;; agent, etc. due to binding conveyance. This may or may not be desirable
+;; depending upon the expectations of the client/user. I'm not sure at the moment
+;; how best to make it configurable though...
+
+(defn- session-out
+ "Returns a PrintWriter suitable for binding as *out* or *err*. All of
+ the content written to that PrintWriter will (when .flush-ed) be sent on the
+ given transport in messages specifying the given session-id.
+ `channel-type` should be :out or :err, as appropriate."
+ [channel-type session-id transport]
+ (let [buf (clojure.tools.nrepl.StdOutBuffer.)]
+ (PrintWriter. (proxy [Writer] []
+ (close [] (.flush ^Writer this))
+ (write [& [x off len]]
+ (locking buf
+ (cond
+ (number? x) (.append buf (char x))
+ (not off) #(.append buf x)
+ (instance? CharSequence x) (.append buf ^CharSequence x off len)
+ :else (.append buf ^chars x off len))))
+ (flush []
+ (let [text (locking buf (let [text (str buf)]
+ (.setLength buf 0)
+ text))]
+ (when (pos? (count text))
+ (t/send transport
+ (response-for *msg* :session session-id
+ channel-type text)))))))))
+
+(defn- session-in
+ "Returns a LineNumberingPushbackReader suitable for binding to *in*.
+ When something attempts to read from it, it will (if empty) send a
+ {:status :need-input} message on the provided transport so the client/user
+ can provide content to be read."
+ [session-id transport]
+ (let [request-input (fn [^PipedReader r]
+ (when-not (.ready r)
+ (t/send transport
+ (response-for *msg* :session session-id
+ :status :need-input))))
+ writer (PipedWriter.)
+ reader (LineNumberingPushbackReader.
+ (proxy [PipedReader] [writer]
+ (close [])
+ (read
+ ([] (request-input this)
+ (let [^Reader this this] (proxy-super read)))
+ ([x] (request-input this)
+ (let [^Reader this this]
+ (if (instance? java.nio.CharBuffer x)
+ (proxy-super read ^java.nio.CharBuffer x)
+ (proxy-super read ^chars x))))
+ ([buf off len]
+ (let [^Reader this this]
+ (request-input this)
+ (proxy-super read buf off len))))))]
+ [reader writer]))
+
+(defn- session-error-handler
+ [session-agent ex]
+ #_(when-not (or (instance? InterruptedException ex)
+ (instance? ThreadDeath ex))
+ )
+ (log ex "Session error, id " (-> session-agent meta :id)))
+
+(defn- create-session
+ "Returns a new agent containing a map of bindings as per
+ `clojure.core/get-thread-bindings`. Values for *out*, *err*, and *in*
+ are obtained using `session-in` and `session-out`, *ns* defaults to 'user,
+ and other bindings as optionally provided in `baseline-bindings` are
+ merged in."
+ ([transport] (create-session transport {}))
+ ([transport baseline-bindings]
+ (clojure.main/with-bindings
+ (let [id (uuid)
+ out (session-out :out id transport)
+ [in in-writer] (session-in id transport)]
+ (binding [*out* out
+ *err* (session-out :err id transport)
+ *in* in
+ *ns* (create-ns 'user)
+ ; clojure.test captures *out* at load-time, so we need to make sure
+ ; runtime output of test status/results is redirected properly
+ ; TODO is this something we need to consider in general, or is this
+ ; specific hack reasonable?
+ clojure.test/*test-out* out]
+ (agent (merge baseline-bindings (get-thread-bindings))
+ :meta {:id id
+ :stdin-writer in-writer}
+ :error-mode :continue
+ :error-handler #'session-error-handler))))))
+
+(defn- register-session
+ "Registers a new session containing the baseline bindings contained in the
+ given message's :session."
+ [{:keys [session transport] :as msg}]
+ (let [session (create-session transport @session)
+ id (-> session meta :id)]
+ (swap! sessions assoc id session)
+ (t/send transport (response-for msg :status :done :new-session id))))
+
+(defn- close-session
+ "Drops the session associated with the given message."
+ [{:keys [session transport] :as msg}]
+ (swap! sessions dissoc (-> session meta :id))
+ (t/send transport (response-for msg :status #{:done :session-closed})))
+
+(defn session
+ "Session middleware. Returns a handler which supports these :op-erations:
+
+ * \"ls-sessions\", which results in a response message
+ containing a list of the IDs of the currently-retained sessions in a
+ :session slot.
+ * \"close\", which drops the session indicated by the
+ ID in the :session slot. The response message's :status will include
+ :session-closed.
+ * \"clone\", which will cause a new session to be retained. The ID of this
+ new session will be returned in a response message in a :new-session
+ slot. The new session's state (dynamic scope, etc) will be a copy of
+ the state of the session identified in the :session slot of the request.
+
+ Messages indicating other operations are delegated to the given handler,
+ with the session identified by the :session ID added to the message. If
+ no :session ID is found, a new session is created (which will only
+ persist for the duration of the handling of the given message).
+
+ Requires the interruptible-eval middleware (specifically, its binding of
+ *msg* to the currently-evaluated message so that session-specific *out*
+ and *err* content can be associated with the originating message)."
+ [h]
+ (fn [{:keys [op session transport] :as msg}]
+ (let [the-session (if session
+ (@sessions session)
+ (create-session transport))]
+ (if-not the-session
+ (t/send transport (response-for msg :status #{:error :unknown-session}))
+ (let [msg (assoc msg :session the-session)]
+ (case op
+ "clone" (register-session msg)
+ "close" (close-session msg)
+ "ls-sessions" (t/send transport (response-for msg :status :done
+ :sessions (keys @sessions)))
+ (h msg)))))))
+
+(defn add-stdin
+ "stdin middleware. Returns a handler that supports a \"stdin\" :op-eration, which
+ adds content provided in a :stdin slot to the session's *in* Reader. Delegates to
+ the given handler for other operations.
+
+ Requires the session middleware."
+ [h]
+ (fn [{:keys [op stdin session transport] :as msg}]
+ (if (= op "stdin")
+ (do
+ (-> session meta ^Writer (:stdin-writer) (.write stdin))
+ (t/send transport (response-for msg :status :done)))
+ (h msg))))
View
16 src/main/clojure/clojure/tools/nrepl/misc.clj
@@ -37,7 +37,7 @@
The :session value in `msg` may be any Clojure reference type (to accommodate
likely implementations of sessions) that has an :id slot in its metadata,
or a string."
- [msg & response-data]
+ [{:keys [session id]} & response-data]
{:pre [(seq response-data)]}
(let [{:keys [status] :as response} (if (map? (first response-data))
(reduce merge response-data)
@@ -46,10 +46,10 @@
response
(assoc response :status (if (coll? status)
status
- #{status})))]
- (-> (select-keys msg [:session :id])
- ; AReference should make this suitable for any session implementation
- (update-in [:session] #(if (instance? clojure.lang.AReference %)
- (-> % meta :id)
- %))
- (merge response))))
+ #{status})))
+ basis (merge (when id {:id id})
+ ; AReference should make this suitable for any session implementation?
+ (when session {:session (if (instance? clojure.lang.AReference session)
+ (-> session meta :id)
+ session)}))]
+ (merge basis response)))
View
45 src/main/clojure/clojure/tools/nrepl/server.clj
@@ -2,19 +2,27 @@
:author "Chas Emerick"}
clojure.tools.nrepl.server
(:require [clojure.tools.nrepl :as repl]
- (clojure.tools.nrepl handlers
- [ack :as ack]
- [transport :as transport]))
+ (clojure.tools.nrepl [ack :as ack]
+ [transport :as t])
+ (clojure.tools.nrepl.middleware interruptible-eval
+ pr-values
+ session))
(:use [clojure.tools.nrepl.misc :only (returning response-for log)])
(:import (java.net Socket ServerSocket)))
+(defn unknown-op
+ "Sends an :unknown-op :error for the given message."
+ [transport {:keys [op] :as msg}]
+ (t/send transport (response-for msg :status #{:error :unknown-op} :op op)))
+
(defn handle
"Handles requests received via `transport` using `handler`.
Returns nil when `recv` returns nil for the given transport."
[handler transport]
- (when-let [msg (transport/recv transport)]
+ (when-let [msg (t/recv transport)]
(try
- (handler (assoc msg :transport transport))
+ (or (handler (assoc msg :transport transport))
+ (unknown-op transport msg))
(catch Throwable t
(log t "Unhandled REPL handler exception processing message" msg)))
(recur handler transport)))
@@ -34,12 +42,32 @@
[server]
(send-off server #(returning % (.close ^ServerSocket (:ss %)))))
+(defn default-handler
+ "A default handler supporting interruptible evaluation, stdin, sessions, and
+ readable representations of evaluated expressions via `pr`."
+ []
+ (-> (constantly false)
+ clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval
+ clojure.tools.nrepl.middleware.pr-values/pr-values
+ clojure.tools.nrepl.middleware.session/add-stdin
+ ; output-subscriptions TODO
+ clojure.tools.nrepl.middleware.session/session))
+
+;; TODO
+#_(defn- output-subscriptions
+ [h]
+ (fn [{:keys [op sub unsub] :as msg}]
+ (case op
+ "sub" ;; TODO
+ "unsub"
+ (h msg))))
+
(defn start-server
"Starts a socket-based nREPL server. Configuration options include:
* :port — defaults to 0, which autoselects an open port on localhost
* :handler — the nREPL message handler to use for each incoming connection;
- defaults to the result of (clojure.tools.nrepl.handlers/default-handler)
+ defaults to the result of (default-handler)
* :transport-fn — a function that, given a java.net.Socket corresponding
to an incoming connection, will return an value satisfying the
clojure.tools.nrepl.Transport protocol for that Socket.
@@ -52,10 +80,9 @@
[& {:keys [port transport-fn handler ack-port greeting-fn] :or {port 0}}]
(let [ss (ServerSocket. port)
smap {:ss ss
- :transport (or transport-fn transport/bencode)
+ :transport (or transport-fn t/bencode)
:greeting greeting-fn
- :handler (or handler
- (clojure.tools.nrepl.handlers/default-handler))}
+ :handler (or handler (default-handler))}
server (proxy [clojure.lang.Agent java.io.Closeable] [smap]
(close [] (stop-server this)))]
(send-off server accept-connection)
View
11 src/test/clojure/clojure/tools/nrepl/sanity_test.clj
@@ -9,7 +9,8 @@
(ns clojure.tools.nrepl.sanity-test
(:use clojure.test
[clojure.tools.nrepl.transport :only (piped-transports)])
- (:require [clojure.tools.nrepl.handlers :as handlers]
+ (:require (clojure.tools.nrepl.middleware [interruptible-eval :as eval]
+ [session :as session])
[clojure.tools.nrepl :as repl]
[clojure.set :as set])
(:import (java.util.concurrent BlockingQueue LinkedBlockingQueue TimeUnit)))
@@ -31,9 +32,9 @@
resp-fn (if ns
(juxt :ns :value)
:value)]
- (handlers/evaluate {#'*out* (java.io.PrintWriter. out)
- #'*err* (java.io.PrintWriter. err)}
- msg)
+ (eval/evaluate {#'*out* (java.io.PrintWriter. out)
+ #'*err* (java.io.PrintWriter. err)}
+ msg)
(->> (repl/response-seq local 0)
(map resp-fn)
(cons (str out))
@@ -87,7 +88,7 @@
(deftest repl-out-writer
(let [[local remote] (piped-transports)
- w (#'handlers/session-out :out :dummy-session-id remote)]
+ w (#'session/session-out :out :dummy-session-id remote)]
(doto w
.flush
(.write "abcd")
View
3 src/test/clojure/clojure/tools/nrepl_test.clj
@@ -2,7 +2,6 @@
(:use clojure.test
clojure.tools.nrepl)
(:require (clojure.tools.nrepl [transport :as transport]
- [handlers :as handlers]
[server :as server]
[ack :as ack])))
@@ -204,7 +203,7 @@
(is (= [2] (response-values (response-seq conn 100))))))
(deftest test-ack
- (with-open [s (server/start-server :handler (ack/handle-ack (handlers/default-handler)))]
+ (with-open [s (server/start-server :handler (ack/handle-ack (server/default-handler)))]
(ack/reset-ack-port!)
(with-open [s2 (server/start-server :ack-port (.getLocalPort (:ss @s)))]
(is (= (.getLocalPort (:ss @s2)) (ack/wait-for-ack 10000))))))

0 comments on commit 4e3317c

Please sign in to comment.
Something went wrong with that request. Please try again.