Skip to content

Commit

Permalink
Add support for alternative response data types *experimental*
Browse files Browse the repository at this point in the history
  • Loading branch information
cemerick committed Oct 26, 2011
1 parent e2f6e2d commit a6851d9
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 7 deletions.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,20 @@ in the provided nREPL client.
- `code` The code to be evaluated.
- `in` A string containing content to be bound (via a Reader) to `*in*` for the duration
of `code`'s execution
- `timeout` The maximum amount of time, in milliseconds, that the provided code will be
allowed to run before a `timeout` response is sent. This is optional; if not provided,
- `timeout` (optional) The maximum amount of time, in milliseconds, that the provided code will be
allowed to run before a `timeout` response is sent. If not provided,
a default timeout will be assigned by the server (currently always 60s).
- `accept` (optional, *experimental*) The data type(s) that the client would like to receive back as
"rendering(s)" of the value of `code`. Each type is provided as a string,
e.g. "jpg" or "txt"; if multiple data types are acceptable, then multiple `accept` pairs
should be sent in the message. Each type should correspond to a different
rendering implementation registered with or provided by the nREPL server; if the value
obtained from evaluating `code` has a rendering implementation matching any of the types in
`accept`, a response message will be sent that contains a `rendered-XXX` slot, where `XXX` is
the type in question. e.g. if `code` will return a graph data structure, then including
"jpg" in `accept` might result in a `rendered-jpg` slot message being returned with image data.
Including "txt" in `accept` might result in a `rendered-txt` slot message being returned
with an ASCII art representation of the graph data structure.

Only `id` and `code` are required in every request.

Expand Down Expand Up @@ -208,6 +219,15 @@ the time of printing, a pretty-printer will be used instead:
2. Clojure Contrib (and therefore `clojure.contrib.pprint`)
2. `clojure.tools.nrepl/*pretty-print*` is `set!`'ed to true (which persists for the
duration of the client connection)
- `rendered-<datatype>` (optional, *experimental*) If the corresponding request contained
data type(s) in its `accept` slot, and the value of the evaluated code sent could be rendered
into the specified data type, then `rendered-<datatype>` will contain the Base64-encoded
binary representation of that datatype. e.g. if the request's `accept` slot included "png",
and an implementation existed to produce an image rendering of the request's `code`'s value,
then a response message will be sent with a `rendered-png` slot containing Base64-encoded
image data. The details of the data returned via messages containing `rendered-<datatype>`
slots is defined solely by the rendering implementations provided by or registered with
the nREPL server.
- `status` One of:
- `error` Indicates an error occurred evaluating the requested code. The related
exception is bound to `*e` per usual, and printed to `*err*`, which will be delivered
Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
<clojure.version>1.2.0</clojure.version>
</properties>

<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>core.incubator</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>

<build>
<resources>
<resource>
Expand Down
19 changes: 14 additions & 5 deletions src/main/clojure/clojure/tools/nrepl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
clojure.tools.nrepl
(:require clojure.main
clojure.stacktrace
clojure.tools.nrepl.helpers
clojure.test)
clojure.test
[clojure.tools.nrepl.response :as response])
(:import (java.net ServerSocket)
(clojure.lang Var LineNumberingPushbackReader)
java.lang.ref.WeakReference
Expand Down Expand Up @@ -135,6 +135,12 @@
[v]
(if (or (coll? v) (instance? java.util.Collection v)) v [v]))

(defn- message-entry-count
[msg]
(apply + (for [[k v] msg
v (as-collection v)]
1)))

;See the README for message format
;
;Not simply printing and reading maps because the client
Expand All @@ -146,7 +152,7 @@
(locking out
(binding [*out* out
*print-readably* true]
(prn (count msg))
(prn (message-entry-count msg))
(doseq [[k v] msg
v (as-collection v)]
(prn (if (string? k) k (name k)))
Expand Down Expand Up @@ -218,7 +224,7 @@
true))

(defn- handle-request
[client-state-atom write-response {:keys [code in interrupt-atom ns] :or {in ""} :as msg}]
[client-state-atom write-response {:keys [code in interrupt-atom ns accept] :or {in ""} :as msg}]
(let [code-reader (LineNumberingPushbackReader. (StringReader. code))
out (create-repl-out :out write-response)
err (create-repl-out :err write-response)]
Expand Down Expand Up @@ -265,7 +271,10 @@
(write-response :value (with-out-str
(if (pretty-print?)
(pprint value)
(prn value))))))
(prn value))))
(when-let [[rendering data-type] (and accept
(response/render->base64 value (as-collection accept)))]
(write-response (keyword (str "rendered-" data-type)) rendering))))
(finally
(pop-thread-bindings)
(.flush out)
Expand Down
42 changes: 42 additions & 0 deletions src/main/clojure/clojure/tools/nrepl/response.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
(ns clojure.tools.nrepl.response
; TODO gah! Can we restrict ourselves to java 1.6 and use javax.xml.bind.DatatypeConverter?
; Or maybe just snag http://migbase64.sourceforge.net? (It's not in central)
(:import (sun.misc BASE64Encoder BASE64Decoder))
(:use [clojure.core.incubator :only (-?>)]))

(defn base64-encode
[bytes]
(.encode (BASE64Encoder.) bytes))

(defn base64-decode
[string]
(.decodeBuffer (BASE64Decoder.) string))

(defmulti render
""
(fn [value data-type] [(type value) (keyword "clojure.tools.nrepl.response" data-type)]))

(defn render->base64
[value data-types]
(->> data-types
(map #(-?> value (render %) base64-encode (vector %)))
(remove nil?)
first))

(derive ::png ::image)
(derive ::jpg ::image)
(derive ::jpeg ::image)
(derive ::bmp ::image)

(defmethod render
[java.awt.image.RenderedImage ::image]
[img image-format]
(let [out (java.io.ByteArrayOutputStream.)
; good grief: can't just use (ImageIO/write img format out) because of http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6967419
img-out (javax.imageio.stream.MemoryCacheImageOutputStream. out)]
; TODO provide some way to control image resolution, format, maybe max file size (hard?)
(javax.imageio.ImageIO/write img (name image-format) img-out)
(doto img-out .flush .close)
(.toByteArray out)))

(defmethod render :default [_ _] nil)

0 comments on commit a6851d9

Please sign in to comment.