pax_global_header00006660000000000000000000000064130704633610014515gustar00rootroot0000000000000052 comment=d9e90010c929610af91ec222aaf4f82fc98c8c8d tools.nrepl-tools.nrepl-0.2.13/000077500000000000000000000000001307046336100163345ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/.gitignore000066400000000000000000000002601307046336100203220ustar00rootroot00000000000000# emacs + vi backup files *~ .*.sw* # various IDE junk *.ipr *.iml *.iws .project .classpath .settings # artifacts, etc eclipse-classes classes target .externalToolBuilders tools.nrepl-tools.nrepl-0.2.13/CHANGELOG.md000066400000000000000000000115251307046336100201510ustar00rootroot00000000000000## Changelog `0.2.13`: * `start-server` now binds to `::` by default, and falls back to `localhost`, avoiding confusion when working in environments that have both IPv4 and IPv6 networking available. (NREPL-83) `0.2.11`: * `clojure.tools.nrepl.middleware.interruptible-eval` now accepts optional `file`, `line`, and `column` values in order to fix location metadata to defined vars and functions, for more useful stack traces, navigation, etc. * REPL evaluations now support use of reader conditionals (loading `.cljc` files containing reader conditionals has always worked transparently) `0.2.10`: * `clojure.tools.nrepl.middleware.pr-values` will _not_ print the contents of `:value` response messages if the message contains a `:printed-value` slot. * `default-executor` and `queue-eval` in `clojure.tools.nrepl.middleware.interruptible-eval` are now public. `0.2.9`: * `clojure.tools.nrepl.middleware.interruptible-eval` now defines a default thread executor used for all evaluations (unless a different executor is provided to the configuration of `clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval`). This should aid in the development of `interrupt`-capable alternative evaluation middlewares/handlers. `0.2.8`: * The default bind address used by `clojure.tools.nrepl.server/start-server` is now `localhost`, not `0.0.0.0`. As always, the bind address can be set explicitly via a `:bind` keyword argument to that function. This is considered a security bugfix, though _technically_ it may cause breakage if anyone was implicitly relying upon nREPL's socket server to listen on all network interfaces. * The `ServerSocket` created as part of `clojure.tools.nrepl.server/start-server` is now configured with `SO_REUSEADDR` enabled; this should prevent spurious "address already in use" when quickly bouncing apps that open an nREPL server on a fixed port, etc. (NREPL-67) * Middlewares may now contribute to the response of the `"describe"` operation via an optional `:describe-fn` function provided via their descriptors. (NREPL-64) * The `:ns` component of the response to `"load-file"` operations is now elided, as it was (usually) incorrect (as a result of reusing `interruptible-eval` for handling `load-file` operations) (NREPL-68) `0.2.7`: * The topological sort ("linearization") applied to middleware provided to start a new nREPL server has been reworked to address certain edge case bugs (NREPL-53) * `interruptible-eval` no longer incorrectly clobbers a session's `*ns*` binding when it processes an `eval` message containing an `ns` "argument" * Eliminated miscellaneous reflection warnings `0.2.5`: * Clients can now signal EOF on `*in*` with an empty `:stdin` value (NREPL-65) * Clojure `:version-string` is now included in response to a `describe` operation (NREPL-63) * Improve representation of `java.version` information in response to a `describe` operation (NREPL-62) `0.2.4`: * Fixed the source of a reliable per-connection thread leak (NREPL-40) * Fix printing of lazy sequences so that `*out*` bindings are properly preserved (NREPL-45) * Enhance `clojure.tools.nrepl.middleware.interruptible-eval/evaluate` so that a custom `eval` function can be provided on a per-message basis (NREPL-50) * Fix pretty-printing of reference returned by `clojure.tools.nrepl.server/start-server` (NREPL-51) * nREPL now works with JDK 1.8 (NREPL-56) * The value of the `java.version` system property is now included in the response to a `describe` operation (NREPL-57) * Common session bindings (e.g. `*e`, `*1`, etc) are now set in time for nREPL middleware to access them in the case of an exception being thrown (NREPL-58) `0.2.3`: * Now using a queue to maintain `*in*`, to avoid intermittent failures due to prior use of `PipedReader`/`Writer`. (NREPL-39) * When loading a file, always bind `*print-level*` and `*print-length*` when generating the `clojure.lang.Compiler/load` expression (NREPL-41) `0.2.2`: * Added `clojure.tools.nrepl/code*` for `pr-str`'ing expressions (presumably for later evaluation) * session IDs are now properly combined into a set by `clojure.tools.nrepl/combine-responses` * fixes printing of server instances under Clojure 1.3.0+ (nREPL-37) `0.2.1`: * fixes incorrect translation between `Writer.write()` and `StringBuilder.append()` APIs (NREPL-38) `0.2.0`: Top-to-bottom redesign `0.0.6`: Never released; initial prototype of "rich content" support that (in part) helped motivate a re-examination of the underlying protocol and design. `0.0.5`: - added Clojure 1.3.0 (ALPHA) compatibility `0.0.4`: - fixed (hacked) obtaining `clojure.test` output when `clojure.test` is initially loaded within an nREPL session - eliminated 1-minute default timeout on expression evaluation - all standard REPL var bindings are now properly established and maintained within a session tools.nrepl-tools.nrepl-0.2.13/CONTRIBUTING.md000066400000000000000000000012211307046336100205610ustar00rootroot00000000000000This is a [Clojure contrib] project. Under the Clojure contrib [guidelines], this project cannot accept pull requests. All patches must be submitted via [JIRA]. See [Contributing] and the [FAQ] on the Clojure development [wiki] for more information on how to contribute. [Clojure contrib]: http://dev.clojure.org/display/doc/Clojure+Contrib [Contributing]: http://dev.clojure.org/display/community/Contributing [FAQ]: http://dev.clojure.org/display/community/Contributing+FAQ [JIRA]: http://dev.clojure.org/jira/browse/NREPL [guidelines]: http://dev.clojure.org/display/community/Guidelines+for+Clojure+Contrib+committers [wiki]: http://dev.clojure.org/ tools.nrepl-tools.nrepl-0.2.13/META-INF/000077500000000000000000000000001307046336100174745ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/META-INF/MANIFEST.MF000066400000000000000000000006031307046336100211250ustar00rootroot00000000000000Manifest-Version: 1.0 Bundle-Name: nREPL Bundle-Version: 0.2.0.BETA010 Bundle-SymbolicName: org.clojure.tools.nrepl Bundle-ManifestVersion: 2 Bundle-RequiredExecutionEnvironment: J2SE-1.5 Built-By: chas Tool: Bnd-0.0.357 Created-By: Apache Maven Bundle Plugin Build-Jdk: 1.6.0_22 Export-Package: clojure.tools, clojure.tools.nrepl Import-Package: clojure;version="1.3.0", clojure.lang tools.nrepl-tools.nrepl-0.2.13/README.md000066400000000000000000000605301307046336100176170ustar00rootroot00000000000000# nREPL [nREPL](http://github.com/clojure/tools.nrepl) is a Clojure *n*etwork REPL that provides a REPL server and client, along with some common APIs of use to IDEs and other tools that may need to evaluate Clojure code in remote environments. ## Usage ### "Installation" nREPL is available in Maven central. Add this to your Leiningen `project.clj` `:dependencies`: ```clojure [org.clojure/tools.nrepl "0.2.12"] ``` Or, add this to your Maven project's `pom.xml`: ```xml org.clojure tools.nrepl 0.2.12 ``` A list of all prior releases are available [here](http://search.maven.org/#search|gav|1|g%3A%22org.clojure%22%20AND%20a%3A%22tools.nrepl%22). Please note the changelog in `CHANGELOG.md`. nREPL is compatible with Clojure 1.2.0 and higher. Please post general questions or discussion on either the [clojure-dev](http://groups.google.com/group/clojure-dev/) or [clojure-tools](http://groups.google.com/group/clojure-tools) mailing lists. Bug reports and such may be filed into [nREPL's JIRA](http://dev.clojure.org/jira/browse/NREPL). nREPL's generated API documentation is available [here](http://clojure.github.com/tools.nrepl/). A [history of nREPL builds](http://build.clojure.org/job/tools.nrepl/) is available, as well as [a compatibility test matrix](http://build.clojure.org/job/tools.nrepl-test-matrix/), verifying nREPL's functionality against multiple versions of Clojure and multiple JVMs. ### Connecting to an nREPL server Most of the time, you will connect to an nREPL server using an existing client/tool. Tools that support nREPL include: * [Leiningen](https://github.com/technomancy/leiningen) (starting with v2) * [Counterclockwise](https://github.com/laurentpetit/ccw) (Clojure IDE/plugin for Eclipse) * [Cursive](https://cursiveclojure.com) (Clojure IDE/plugin for IntelliJ Idea) * [cider](https://github.com/clojure-emacs/cider) (Clojure IDE and REPL for Emacs) * [monroe](https://github.com/sanel/monroe) (nREPL client for Emacs) * [fireplace.vim](https://github.com/tpope/vim-fireplace) (Clojure + nREPL support for vim) * [Reply](https://github.com/trptcolin/reply/) * [Atom](https://atom.io/packages/search?q=nrepl) If your preferred Clojure development environment supports nREPL, you're done. Use it or connect to an existing nREPL endpoint, and you're done. #### Talking to an nREPL endpoint programmatically If you want to connect to an nREPL server using the default transport, something like this will work: ```clojure => (require '[clojure.tools.nrepl :as repl]) nil => (with-open [conn (repl/connect :port 59258)] (-> (repl/client conn 1000) ; message receive timeout required (repl/message {:op "eval" :code "(+ 2 3)"}) repl/response-values)) [5] ``` `response-values` will return only the values of evaluated expressions, read from their (by default) `pr`-encoded representations via `read`. You can see the full content of message responses easily: ```clojure => (with-open [conn (repl/connect :port 59258)] (-> (repl/client conn 1000) (repl/message {:op :eval :code "(time (reduce + (range 1e6)))"}) doall ;; `message` and `client-session` all return lazy seqs pprint)) nil ({:out "\"Elapsed time: 68.032 msecs\"\n", :session "2ba81681-5093-4262-81c5-edddad573201", :id "3124d886-7a5d-4c1e-9fc3-2946b1b3cfaa"} {:ns "user", :value "499999500000", :session "2ba81681-5093-4262-81c5-edddad573201", :id "3124d886-7a5d-4c1e-9fc3-2946b1b3cfaa"} {:status ["done"], :session "2ba81681-5093-4262-81c5-edddad573201", :id "3124d886-7a5d-4c1e-9fc3-2946b1b3cfaa"}) ``` Each message must contain at least an `:op` (or `"op"`) slot, which specifies the "type" of the operation to be performed. The operations supported by an nREPL endpoint are determined by the handlers and middleware stack used when starting that endpoint; the default middleware stack (described below) supports a particular set of operations, [detailed here](https://github.com/clojure/tools.nrepl/blob/master/doc/ops.md). ### Embedding nREPL, starting a server If your project uses Leiningen (v2 or higher), you already have access to an nREPL server for your project via `lein repl` (or, `lein repl :headless` if you don't need the Reply terminal-based nREPL client to connect to the resulting nREPL server). Otherwise, it can be extremely useful to have your application host a REPL server whereever it might be deployed; this can greatly simplify debugging, sanity-checking, panicked code patching, and so on. nREPL provides a socket-based server that you can trivially start from your application. [Add it to your project's dependencies](#installing), and add code like this to your app: ```clojure => (use '[clojure.tools.nrepl.server :only (start-server stop-server)]) nil => (defonce server (start-server :port 7888)) #'user/server ``` Depending on what the lifecycle of your application is, whether you want to be able to easily restart the server, etc., you might want to put the value `start-server` returns into an atom or somesuch. Anyway, once your app is running an nREPL server, you can connect to it from a tool like Leiningen or Counterclockwise or Reply, or from another Clojure process: ```clojure => (with-open [conn (repl/connect :port 7888)] (-> (repl/client conn 1000) (repl/message {:op :eval :code "(+ 1 1)"}) repl/response-values)) [2] ``` You can stop the server with `(stop-server server)`. #### Server options Note that nREPL is not limited to its default messaging protocol, nor to its default use of sockets. nREPL provides a _transport_ abstraction for implementing support for alternative protocols and connection methods. Alternative transport implementations are available, and implementing your own is not difficult; read more about transports [here](#transports). ### Building nREPL Releases are available from Maven Central, and SNAPSHOT builds from master's HEAD are automatically deployed to Sonatype's OSS repository (see [this](http://dev.clojure.org/display/doc/Maven+Settings+and+Repositories) for how to configure Leiningen or Maven to use OSS-snapshots), so building nREPL shouldn't ever be necessary. But, if you insist: 0. Clone the repo 1. Make sure you have maven installed 2. Run the maven build, either: 1. `mvn package`: This will produce an nREPL jar file in the `target` directory, and run all tests against Clojure 1.2.0. 2. `mvn verify`: This does the same, but also runs the tests with other Clojure "profiles" (one for each supported version of Clojure). ## Why nREPL? nREPL has been designed with the aim of ensuring that it satisfies the requirements of both application developers (in support of activities ranging from interactive remote debugging and experimentation in development contexts through to more advanced use cases such as updating deployed applications) as well as toolmakers (providing a standard way to connect to and introspect running environments as a way of informing user interfaces of all kinds, including "standard" interactive, text-based REPLs). The default network protocol used is simple, depending neither on JVM or Clojure specifics, thereby allowing (encouraging?) the development of non-Clojure REPL clients. The REPLs operational semantics are such that essentially any non-JVM Clojure implementation should be able to implement it, with allowances for hosts that lack the concurrency primitives to support e.g. asynchronous evaluation, interrupts, etc. For more information about the motivation, architecture, use cases, and discussion related to nREPL, see the see the original design notes, available [here](https://docs.google.com/document/edit?id=1dnb1ONTpK9ttO5W4thxiXkU5Ki89gK62anRqKEK4YZI&authkey=CMuszuMI&hl=en#), and the [notes](https://github.com/clojure/tools.nrepl/wiki/nREPL.Next) and [discussion](http://groups.google.com/group/clojure-dev/browse_frm/thread/6e366c1d0eaeec59) around its recent redesign. ### Design nREPL largely consists of three abstractions: handlers, middleware, and transports. These are roughly analogous to the handlers, middleware, and adapters of [Ring](https://github.com/ring-clojure/ring), though there are some important semantic differences. Finally, nREPL is fundamentally message-oriented and asynchronous (in contrast to most REPLs that build on top of streams provided by e.g. terminals). #### Messages nREPL messages are maps. The keys and values that may be included in messages depends upon the transport being used; different transports may encode messages differently, and therefore may or may not be able to represent certain data types. Each message sent to an nREPL endpoint constitutes a "request" to perform a particular operation, which is indicated by a `"op"` entry. Each operation may further require the incoming message to contain other data. Which data an operation requires or may accept varies; for example, a message to evaluate some code might look like this: ```clojure {"op" "eval" "code" "(+ 1 2 3)"} ``` The result(s) of performing each operation may be sent back to the nREPL client in one or more response messages, the contents of which again depend upon the operation. #### Transports _Transports_ are roughly analogous to Ring's adapters: they provide an implementation of a common protocol (`clojure.tools.nrepl.transport.Transport`) to enable nREPL clients and servers to send and receive messages without regard for the underlying channel or particulars of message encoding. nREPL includes two transports, both of which are socket-based: a "tty" transport that allows one to connect to an nREPL endpoint using e.g. `telnet` (which therefore supports only the most simplistic interactive evaluation of expressions), and one that uses [bencode](http://wiki.theory.org/BitTorrentSpecification#Bencoding) to encode nREPL messages over sockets. It is the latter that is used by default by `clojure.tools.nrepl.server/start-server` and `clojure.tools.nrepl/connect`. [Other nREPL transports are provided by the community] (https://github.com/clojure/tools.nrepl/wiki/Extensions). #### Handlers _Handlers_ are functions that accept a single incoming message as an argument. An nREPL server is started with a single handler function, which will be used to process messages for the lifetime of the server. Note that handler return values are _ignored_; results of performing operations should be sent back to the client via the transport in use (which will be explained shortly). This may seem peculiar, but is motivated by two factors: * Many operations — including something as simple as code evaluation — is fundamentally asynchronous with respect to the nREPL server * Many operations can produce multiple results (e.g. evaluating a snippet of code like `"(+ 1 2) (def a 6)"`). Thus, messages provided to nREPL handlers are guaranteed to contain a `:transport` entry containing the [transport](#transports) that should be used to send all responses precipitated by a given message. (This slot is added by the nREPL server itself, thus, if a client sends any message containing a `"transport"` entry, it will be bashed out by the `Transport` that was the source of the message.) Further, all messages provided to nREPL handlers have keyword keys (as per `clojure.walk/keywordize-keys`). Depending on its `:op`, a message might be required to contain other slots, and might optionally contain others. It is generally the case that request messages should contain a globally-unique `:id`. Every request must provoke at least one and potentially many response messages, each of which should contain an `:id` slot echoing that of the provoking request. Once a handler has completely processed a message, a response containing a `:status` of `:done` must be sent. Some operations necessitate that additional responses related to the processing of a request are sent after a `:done` `:status` is reported (e.g. delivering content written to `*out*` by evaluated code that started a `future`). Other statuses are possible, depending upon the semantics of the `:op` being handled; in particular, if the message is malformed or incomplete for a particular `:op`, then a response with an `:error` `:status` should be sent, potentially with additional information about the nature of the problem. It is possible for an nREPL server to send messages to a client that are not a direct response to a request (e.g. streaming content written to `System/out` might be started/stopped by requests, but messages containing such content can't be considered responses to those requests). If the handler being used by an nREPL server does not recognize or cannot perform the operation indicated by a request message's `:op`, then it should respond with a message containing a `:status` of `"unknown-op"`. It is currently the case that the handler provided as the `:handler` to `clojure.tools.nrepl.server/start-server` is generally built up as a result of composing multiple pieces of middleware. #### Middleware _Middleware_ are higher-order functions that accept a handler and return a new handler that may compose additional functionality onto or around the original. For example, some middleware that handles a hypothetical `"time?"` `:op` by replying with the local time on the server: ```clojure (require '[clojure.tools.nrepl.transport :as t]) (use '[clojure.tools.nrepl.misc :only (response-for)]) (defn current-time [h] (fn [{:keys [op transport] :as msg}] (if (= "time?" op) (t/send transport (response-for msg :status :done :time (System/currentTimeMillis))) (h msg)))) ``` A little silly, but this pattern should be familiar to you if you have implemented Ring middleware before. Nearly all of the same patterns and expectations associated with Ring middleware should be applicable to nREPL middleware. All of nREPL's provided default functionality is implemented in terms of middleware, even foundational bits like session and eval support. This default middleware "stack" aims to match and exceed the functionality offered by the standard Clojure REPL, and is available at `clojure.tools.nrepl.server/default-middlewares`. Concretely, it consists of a number of middleware functions' vars that are implicitly merged with any user-specified middleware provided to `clojure.tools.nrepl.server/default-handler`. To understand how that implicit merge works, we'll first need to talk about middleware "descriptors". [Other nREPL middlewares are provided by the community] (https://github.com/clojure/tools.nrepl/wiki/Extensions). (See [this documentation listing](https://github.com/clojure/tools.nrepl/blob/master/doc/ops.md) for details as to the operations implemented by nREPL's default middleware stack, what each operation expects in request messages, and what they emit for responses.) ##### Middleware descriptors and nREPL server configuration It is generally the case that most users of nREPL will expect some minimal REPL functionality to always be available: evaluation (and the ability to interrupt evaluations), sessions, file loading, and so on. However, as with all middleware, the order in which nREPL middleware is applied to a base handler is significant; e.g., the session middleware's handler must look up a user's session and add it to the message map before delegating to the handler it wraps (so that e.g. evaluation middleware can use that session data to stand up the user's dynamic evaluation context). If middleware were "just" functions, then any customization of an nREPL middleware stack would need to explicitly repeat all of the defaults, except for the edge cases where middleware is to be appended or prepended to the default stack. To eliminate this tedium, the vars holding nREPL middleware functions may have a descriptor applied to them to specify certain constraints in how that middleware is applied. For example, the descriptor for the `clojure.tools.nrepl.middleware.session/add-stdin` middleware is set thusly: ```clojure (set-descriptor! #'add-stdin {:requires #{#'session} :expects #{"eval"} :handles {"stdin" {:doc "Add content from the value of \"stdin\" to *in* in the current session." :requires {"stdin" "Content to add to *in*."} :optional {} :returns {"status" "A status of \"need-input\" will be sent if a session's *in* requires content in order to satisfy an attempted read operation."}}}}) ``` Middleware descriptors are implemented as a map in var metadata under a `:clojure.tools.nrepl.middleware/descriptor` key. Each descriptor can contain any of three entries: * `:requires`, a set containing strings or vars identifying other middleware that must be applied at a higher level than the middleware being described. Var references indicate an implementation detail dependency; string values indicate a dependency on _any_ middleware that handles the specified `:op`. * `:expects`, the same as `:requires`, except the referenced middleware must exist in the final stack at a lower level than the middleware being described. * `:handles`, a map that documents the operations implemented by the middleware. Each entry in this map must have as its key the string value of the handled `:op` and a value that contains any of four entries: * `:doc`, a human-readable docstring for the middleware * `:requires`, a map of slots that the handled operation must find in request messages with the indicated `:op` * `:optional`, a map of slots that the handled operation may utilize from the request messages with the indicated `:op` * `:returns`, a map of slots that may be found in messages sent in response to handling the indicated `:op` The values in the `:handles` map is used to support the `"describe"` operation, which provides "a machine- and human-readable directory and documentation for the operations supported by an nREPL endpoint" (see `clojure.tools.nrepl.middleware/describe-markdown`, and the results of `"describe"` and `describe-markdown` [here](https://github.com/clojure/tools.nrepl/blob/master/doc/ops.md)). The `:requires` and `:expects` entries control the order in which middleware is applied to a base handler. In the `add-stdin` example above, that middleware will be applied after any middleware that handles the `"eval"` operation, but before the `clojure.tools.nrepl.middleware.session/session` middleware. In the case of `add-stdin`, this ensures that incoming messages hit the session middleware (thus ensuring that the user's dynamic scope — including `*in*` — has been added to the message) before the `add-stdin`'s handler sees them, so that it may append the provided `stdin` content to the buffer underlying `*in*`. Additionally, `add-stdin` must be "above" any `eval` middleware, as it takes responsibility for calling `clojure.main/skip-if-eol` on `*in*` prior to each evaluation (in order to ensure functional parity with Clojure's default stream-based REPL implementation). The specific contents of a middleware's descriptor depends entirely on its objectives: which operations it is to implement/define, how it is to modify incoming request messages, and which higher- and lower-level middlewares are to aid in accomplishing its aims. nREPL uses the dependency information in descriptors in order to produce a linearization of a set of middleware; this linearization is exposed by `clojure.tools.nrepl.middleware/linearize-middleware-stack`, which is implicitly used by `clojure.tools.nrepl.server/default-handler` to combine the default stack of middleware with any additional provided middleware vars. The primary contribution of `default-handler` is to use `clojure.tools.nrepl.server/unknown-op` as the base handler; this ensures that unhandled messages will always produce a response message with an `:unknown-op` `:status`. Any handlers otherwise created (e.g. via direct usage of `linearize-middleware-stack` to obtain a ordered sequence of middleware vars) should do the same, or use a similar alternative base handler. ## Thanks Thanks to the following Clojure masters for their helpful feedback during the initial design phases of nREPL: * Justin Balthrop * Meikel Brandmeyer * Hugo Duncan * Christophe Grand * Anthony Grimes * Phil Hagelberg * Rich Hickey * Chris Houser * Colin Jones * Laurent Petit * Eric Thorsen ## License Copyright © 2010 - 2013 Chas Emerick and contributors. Licensed under the EPL. (See the file epl.html.) tools.nrepl-tools.nrepl-0.2.13/doc/000077500000000000000000000000001307046336100171015ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/doc/ops.md000066400000000000000000000112011307046336100202170ustar00rootroot00000000000000 # Supported nREPL operations generated from a verbose 'describe' response (nREPL v0.2.11-SNAPSHOT) ## Operations ### `:clone` Clones the current session, returning the ID of the newly-created session. ###### Required parameters ###### Optional parameters * `:session` The ID of the session to be cloned; if not provided, a new session with default bindings is created, and mapped to the returned session ID. ###### Returns * `:new-session` The ID of the new session. ### `:close` Closes the specified session. ###### Required parameters * `:session` The ID of the session to be closed. ###### Optional parameters ###### Returns ### `:describe` Produce a machine- and human-readable directory and documentation for the operations supported by an nREPL endpoint. ###### Required parameters ###### Optional parameters * `:verbose?` Include informational detail for each "op"eration in the return message. ###### Returns * `:aux` Map of auxilliary data contributed by all of the active nREPL middleware via :describe-fn functions in their descriptors. * `:ops` Map of "op"erations supported by this nREPL endpoint * `:versions` Map containing version maps (like \*clojure-version\*, e.g. major, minor, incremental, and qualifier keys) for values, component names as keys. Common keys include "nrepl" and "clojure". ### `:eval` Evaluates code. ###### Required parameters * `:code` The code to be evaluated. * `:session` The ID of the session within which to evaluate the code. ###### Optional parameters * `:column` The column number in [file] at which [code] starts. * `:eval` A fully-qualified symbol naming a var whose function value will be used to evaluate [code], instead of `clojure.core/eval` (the default). * `:file` The path to the file containing [code]. `clojure.core/\*file\*` will be bound to this. * `:id` An opaque message ID that will be included in responses related to the evaluation, and which may be used to restrict the scope of a later "interrupt" operation. * `:line` The line number in [file] at which [code] starts. ###### Returns * `:ex` The type of exception thrown, if any. If present, then `values` will be absent. * `:ns` \*ns\*, after successful evaluation of `code`. * `:root-ex` The type of the root exception thrown, if any. If present, then `values` will be absent. * `:values` The result of evaluating `code`, often `read`able. This printing is provided by the `pr-values` middleware, and could theoretically be customized. Superseded by `ex` and `root-ex` if an exception occurs during evaluation. ### `:interrupt` Attempts to interrupt some code evaluation. ###### Required parameters * `:session` The ID of the session used to start the evaluation to be interrupted. ###### Optional parameters * `:interrupt-id` The opaque message ID sent with the original "eval" request. ###### Returns * `:status` 'interrupted' if an evaluation was identified and interruption will be attempted 'session-idle' if the session is not currently evaluating any code 'interrupt-id-mismatch' if the session is currently evaluating code sent using a different ID than specified by the "interrupt-id" value ### `:load-file` Loads a body of code, using supplied path and filename info to set source file and line number metadata. Delegates to underlying "eval" middleware/handler. ###### Required parameters * `:file` Full contents of a file of code. ###### Optional parameters * `:file-name` Name of source file, e.g. io.clj * `:file-path` Source-path-relative path of the source file, e.g. clojure/java/io.clj ###### Returns * `:ex` The type of exception thrown, if any. If present, then `values` will be absent. * `:ns` \*ns\*, after successful evaluation of `code`. * `:root-ex` The type of the root exception thrown, if any. If present, then `values` will be absent. * `:values` The result of evaluating `code`, often `read`able. This printing is provided by the `pr-values` middleware, and could theoretically be customized. Superseded by `ex` and `root-ex` if an exception occurs during evaluation. ### `:ls-sessions` Lists the IDs of all active sessions. ###### Required parameters ###### Optional parameters ###### Returns * `:sessions` A list of all available session IDs. ### `:stdin` Add content from the value of "stdin" to \*in\* in the current session. ###### Required parameters * `:stdin` Content to add to \*in\*. ###### Optional parameters ###### Returns * `:status` A status of "need-input" will be sent if a session's \*in\* requires content in order to satisfy an attempted read operation. tools.nrepl-tools.nrepl-0.2.13/epl.html000066400000000000000000000305351307046336100200100ustar00rootroot00000000000000 Eclipse Public License - Version 1.0

Eclipse Public License - v 1.0

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.

1. DEFINITIONS

"Contribution" means:

a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and

b) in the case of each subsequent Contributor:

i) changes to the Program, and

ii) additions to the Program;

where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.

"Contributor" means any person or entity that distributes the Program.

"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.

"Program" means the Contributions distributed in accordance with this Agreement.

"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.

2. GRANT OF RIGHTS

a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.

b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.

c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.

d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.

3. REQUIREMENTS

A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:

a) it complies with the terms and conditions of this Agreement; and

b) its license agreement:

i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;

ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;

iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and

iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.

When the Program is made available in source code form:

a) it must be made available under this Agreement; and

b) a copy of this Agreement must be included with each copy of the Program.

Contributors may not remove or alter any copyright notices contained within the Program.

Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.

4. COMMERCIAL DISTRIBUTION

Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.

For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.

5. NO WARRANTY

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.

6. DISCLAIMER OF LIABILITY

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. GENERAL

If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.

If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.

All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.

Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.

This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.

tools.nrepl-tools.nrepl-0.2.13/load-file-test/000077500000000000000000000000001307046336100211455ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/load-file-test/clojure/000077500000000000000000000000001307046336100226105ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/load-file-test/clojure/tools/000077500000000000000000000000001307046336100237505ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/load-file-test/clojure/tools/nrepl/000077500000000000000000000000001307046336100250705ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/load-file-test/clojure/tools/nrepl/load_file_sample.clj000066400000000000000000000002161307046336100310400ustar00rootroot00000000000000(ns clojure.tools.nrepl.load-file-sample) (defn dfunction "Ensure \t that \n the \r various \f escapes \" work \\ as expected \\\"" [])tools.nrepl-tools.nrepl-0.2.13/pom.xml000066400000000000000000000057321307046336100176600ustar00rootroot00000000000000 4.0.0 tools.nrepl 0.2.13 tools.nrepl org.clojure pom.contrib 0.2.2 Chas Emerick http://cemerick.com cemerick@snowtide.com -5 scm:git:git@github.com:clojure/tools.nrepl.git scm:git:git@github.com:clojure/tools.nrepl.git git@github.com:clojure/tools.nrepl.git tools.nrepl-0.2.13 1.2.0 true org.clojure tools.logging 0.2.3 true src/main/resources true src/main/clojure maven-invoker-plugin 1.5 src/integration ${project.build.directory}/integration */pom.xml true clojure:test integration-test install run org.apache.maven.plugins maven-jar-plugin META-INF/MANIFEST.MF tools.nrepl-tools.nrepl-0.2.13/src/000077500000000000000000000000001307046336100171235ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/integration/000077500000000000000000000000001307046336100214465ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.3.0/000077500000000000000000000000001307046336100235465ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.3.0/pom.xml000066400000000000000000000031671307046336100250720ustar00rootroot00000000000000 4.0.0 clojure.tools 0.0.2-SNAPSHOT nrepl-test-clojure-1.3.0 nREPL (Clojure 1.3.0 tests) org.clojure clojure 1.3.0 @project.groupId@ @project.artifactId@ @project.version@ com.theoryinpractise clojure-maven-plugin 1.3.8 @basedir@/src/test/clojure -Dnrepl.basedir=@basedir@ test-clojure test test tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.4.0/000077500000000000000000000000001307046336100235475ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.4.0/pom.xml000066400000000000000000000031671307046336100250730ustar00rootroot00000000000000 4.0.0 clojure.tools 0.0.2-SNAPSHOT nrepl-test-clojure-1.4.0 nREPL (Clojure 1.4.0 tests) org.clojure clojure 1.4.0 @project.groupId@ @project.artifactId@ @project.version@ com.theoryinpractise clojure-maven-plugin 1.3.8 @basedir@/src/test/clojure -Dnrepl.basedir=@basedir@ test-clojure test test tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.5.0/000077500000000000000000000000001307046336100235505ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.5.0/pom.xml000066400000000000000000000040531307046336100250670ustar00rootroot00000000000000 4.0.0 clojure.tools 0.0.2-SNAPSHOT nrepl-test-clojure-1.5.0 nREPL (Clojure 1.5.0 tests) org.clojure clojure 1.5.0 @project.groupId@ @project.artifactId@ @project.version@ false true sonatype-nexus-snapshots Sonatype Nexus Snapshots https://oss.sonatype.org/content/repositories/snapshots com.theoryinpractise clojure-maven-plugin 1.3.8 @basedir@/src/test/clojure -Dnrepl.basedir=@basedir@ test-clojure test test tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.6/000077500000000000000000000000001307046336100234135ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.6/pom.xml000066400000000000000000000040531307046336100247320ustar00rootroot00000000000000 4.0.0 clojure.tools 0.0.2-SNAPSHOT nrepl-test-clojure-1.6.x nREPL (Clojure 1.6.x tests) org.clojure clojure 1.6.0 @project.groupId@ @project.artifactId@ @project.version@ false true sonatype-nexus-snapshots Sonatype Nexus Snapshots https://oss.sonatype.org/content/repositories/snapshots com.theoryinpractise clojure-maven-plugin 1.3.8 @basedir@/src/test/clojure -Dnrepl.basedir=@basedir@ test-clojure test test tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.7/000077500000000000000000000000001307046336100234145ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/integration/clojure-1.7/pom.xml000066400000000000000000000040731307046336100247350ustar00rootroot00000000000000 4.0.0 clojure.tools 0.0.2-SNAPSHOT nrepl-test-clojure-1.7.x nREPL (Clojure 1.7.x tests) org.clojure clojure 1.7.0-master-SNAPSHOT @project.groupId@ @project.artifactId@ @project.version@ false true sonatype-nexus-snapshots Sonatype Nexus Snapshots https://oss.sonatype.org/content/repositories/snapshots com.theoryinpractise clojure-maven-plugin 1.3.8 @basedir@/src/test/clojure -Dnrepl.basedir=@basedir@ test-clojure test test tools.nrepl-tools.nrepl-0.2.13/src/main/000077500000000000000000000000001307046336100200475ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/000077500000000000000000000000001307046336100215125ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/000077500000000000000000000000001307046336100231555ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/000077500000000000000000000000001307046336100243155ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl.clj000066400000000000000000000232671307046336100261410ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns ^{:doc "High level nREPL client support." :author "Chas Emerick"} clojure.tools.nrepl (:require [clojure.tools.nrepl.transport :as transport] clojure.set [clojure.java.io :as io]) (:use [clojure.tools.nrepl.misc :only (uuid)]) (:import clojure.lang.LineNumberingPushbackReader (java.io Reader StringReader Writer PrintWriter))) (defn response-seq "Returns a lazy seq of messages received via the given Transport. Called with no further arguments, will block waiting for each message. The seq will end only when the underlying Transport is closed (i.e. returns nil from `recv`) or if a message takes longer than `timeout` millis to arrive." ([transport] (response-seq transport Long/MAX_VALUE)) ([transport timeout] (take-while identity (repeatedly #(transport/recv transport timeout))))) (defn client "Returns a fn of zero and one argument, both of which return the current head of a single response-seq being read off of the given client-side transport. The one-arg arity will send a given message on the transport before returning the seq. Most REPL interactions are best performed via `message` and `client-session` on top of a client fn returned from this fn." [transport response-timeout] (let [latest-head (atom nil) update #(swap! latest-head (fn [[timestamp seq :as head] now] (if (< timestamp now) [now %] head)) ; nanoTime appropriate here; looking to maintain ordering, not actual timestamps (System/nanoTime)) tracking-seq (fn tracking-seq [responses] (lazy-seq (if (seq responses) (let [rst (tracking-seq (rest responses))] (update rst) (cons (first responses) rst)) (do (update nil) nil)))) restart #(let [head (-> transport (response-seq response-timeout) tracking-seq)] (reset! latest-head [0 head]) head)] ^{::transport transport ::timeout response-timeout} (fn this ([] (or (second @latest-head) (restart))) ([msg] (transport/send transport msg) (this))))) (defn- take-until "Like (take-while (complement f) coll), but includes the first item in coll that returns true for f." [f coll] (let [[head tail] (split-with (complement f) coll)] (concat head (take 1 tail)))) (defn- delimited-transport-seq [client termination-statuses delimited-slots] (with-meta (comp (partial take-until (comp #(seq (clojure.set/intersection % termination-statuses)) set :status)) (let [keys (keys delimited-slots)] (partial filter #(= delimited-slots (select-keys % keys)))) client #(merge % delimited-slots)) (-> (meta client) (update-in [::termination-statuses] (fnil into #{}) termination-statuses) (update-in [::taking-until] merge delimited-slots)))) (defn message "Sends a message via [client] with a fixed message :id added to it. Returns the head of the client's response seq, filtered to include only messages related to the message :id that will terminate upon receipt of a \"done\" :status." [client {:keys [id] :as msg :or {id (uuid)}}] (let [f (delimited-transport-seq client #{"done"} {:id id})] (f (assoc msg :id id)))) (defn new-session "Provokes the creation and retention of a new session, optionally as a clone of an existing retained session, the id of which must be provided as a :clone kwarg. Returns the new session's id." [client & {:keys [clone]}] (let [resp (first (message client (merge {:op "clone"} (when clone {:session clone}))))] (or (:new-session resp) (throw (IllegalStateException. (str "Could not open new session; :clone response: " resp)))))) (defn client-session "Returns a function of one argument. Accepts a message that is sent via the client provided with a fixed :session id added to it. Returns the head of the client's response seq, filtered to include only messages related to the :session id that will terminate when the session is closed." [client & {:keys [session clone]}] (let [session (or session (apply new-session client (when clone [:clone clone])))] (delimited-transport-seq client #{"session-closed"} {:session session}))) (defn combine-responses "Combines the provided seq of response messages into a single response map. Certain message slots are combined in special ways: - only the last :ns is retained - :value is accumulated into an ordered collection - :status and :session are accumulated into a set - string values (associated with e.g. :out and :err) are concatenated" [responses] (reduce (fn [m [k v]] (case k (:id :ns) (assoc m k v) :value (update-in m [k] (fnil conj []) v) :status (update-in m [k] (fnil into #{}) v) :session (update-in m [k] (fnil conj #{}) v) (if (string? v) (update-in m [k] #(str % v)) (assoc m k v)))) {} (apply concat responses))) (defn code* "Returns a single string containing the pr-str'd representations of the given expressions." [& expressions] (apply str (map pr-str expressions))) (defmacro code "Expands into a string consisting of the macro's body's forms (literally, no interpolation/quasiquoting of locals or other references), suitable for use in an :eval message, e.g.: {:op :eval, :code (code (+ 1 1) (slurp \"foo.txt\"))}" [& body] (apply code* body)) (defn read-response-value "Returns the provided response message, replacing its :value string with the result of (read)ing it. Returns the message unchanged if the :value slot is empty or not a string." [{:keys [value] :as msg}] (if-not (string? value) msg (try (assoc msg :value (read-string value)) (catch Exception e (throw (IllegalStateException. (str "Could not read response value: " value) e)))))) (defn response-values "Given a seq of responses (as from response-seq or returned from any function returned by client or client-session), returns a seq of values read from :value slots found therein." [responses] (->> responses (map read-response-value) combine-responses :value)) (defn connect "Connects to a socket-based REPL at the given host (defaults to localhost) and port, returning the Transport (by default clojure.tools.nrepl.transport/bencode) for that connection. Transports are most easily used with `client`, `client-session`, and `message`, depending on the semantics desired." [& {:keys [port host transport-fn] :or {transport-fn transport/bencode host "localhost"}}] {:pre [transport-fn port]} (transport-fn (java.net.Socket. ^String host (int port)))) (defn- ^java.net.URI to-uri [x] {:post [(instance? java.net.URI %)]} (if (string? x) (java.net.URI. x) x)) (defn- socket-info [x] (let [uri (to-uri x) port (.getPort uri)] (merge {:host (.getHost uri)} (when (pos? port) {:port port})))) (def ^{:private false} uri-scheme #(-> (to-uri %) .getScheme .toLowerCase)) (defmulti url-connect "Connects to an nREPL endpoint identified by the given URL/URI. Valid examples include: nrepl://192.168.0.12:7889 telnet://localhost:5000 http://your-app-name.heroku.com/repl This is a multimethod that dispatches on the scheme of the URI provided (which can be a string or java.net.URI). By default, implementations for nrepl (corresponding to using the default bencode transport) and telnet (using the clojure.tools.nrepl.transport/tty transport) are registered. Alternative implementations may add support for other schemes, such as HTTP, HTTPS, JMX, existing message queues, etc." uri-scheme) ;; TODO oh so ugly (defn- add-socket-connect-method! [protocol connect-defaults] (defmethod url-connect protocol [uri] (apply connect (mapcat identity (merge connect-defaults (socket-info uri)))))) (add-socket-connect-method! "nrepl" {:transport-fn transport/bencode :port 7888}) (add-socket-connect-method! "telnet" {:transport-fn transport/tty}) (defmethod url-connect :default [uri] (throw (IllegalArgumentException. (format "No nREPL support known for scheme %s, url %s" (uri-scheme uri) uri)))) (def ^{:doc "Current version of nREPL, map of :major, :minor, :incremental, and :qualifier."} version (when-let [in (.getResourceAsStream (class connect) "/clojure/tools/nrepl/version.txt")] (with-open [^java.io.BufferedReader reader (io/reader in)] (let [version-string (-> reader .readLine .trim)] (assoc (->> version-string (re-find #"(\d+)\.(\d+)\.(\d+)-?(.*)") rest (zipmap [:major :minor :incremental :qualifier])) :version-string version-string))))) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/000077500000000000000000000000001307046336100254355ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/ack.clj000066400000000000000000000033451307046336100266720ustar00rootroot00000000000000 (ns clojure.tools.nrepl.ack (:require [clojure.tools.nrepl :as repl] [clojure.tools.nrepl.transport :as t]) (:import (java.util.concurrent Future TimeUnit TimeoutException))) ; could be a lot fancier, but it'll do for now (def ^{:private true} ack-port-promise (atom nil)) (defn reset-ack-port! [] (reset! ack-port-promise (promise)) ; save people the misery of ever trying to deref the empty promise in their REPL nil) (defn wait-for-ack "Waits for a presumably just-launched nREPL server to connect and deliver its port number. Returns that number if it's delivered within `timeout` ms, otherwise nil. Assumes that `ack` middleware has been applied to the local nREPL server handler. Expected usage: (reset-ack-port!) (start-server already-running-server-port) => (wait-for-ack) 59872 ; the port of the server started via start-server" [timeout] (let [^Future f (future @@ack-port-promise)] (try ; no deref with timeout in 1.2 (.get f timeout TimeUnit/MILLISECONDS) (catch TimeoutException e)))) (defn handle-ack [h] (fn [{:keys [op port transport] :as msg}] (if (not= op "ack") (h msg) (try (deliver @ack-port-promise port) (t/send transport {:status :done}))))) ; TODO could stand to have some better error handling around all of this (defn send-ack [my-port ack-port] (with-open [^java.io.Closeable transport (repl/connect :port ack-port)] (let [client (repl/client transport 1000)] ; consume response from the server, solely to let that side ; finish cleanly without (by default) spewing a SocketException when ; the ack client goes away suddenly (dorun (repl/message client {:op :ack :port my-port}))))) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/bencode.clj000066400000000000000000000334441307046336100275360ustar00rootroot00000000000000;- ; Copyright (c) Meikel Brandmeyer. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns #^{:author "Meikel Brandmeyer" :doc "A netstring and bencode implementation for Clojure."} clojure.tools.nrepl.bencode (:require [clojure.java.io :as io]) (:import (java.io IOException EOFException ByteArrayOutputStream InputStream OutputStream PushbackInputStream) clojure.lang.RT)) ;; # Motivation ;; ;; In each and every application, which contacts peer processes via some ;; communication channel, the handling of the communication channel is ;; obviously a central part of the application. Unfortunately introduces ;; handling of buffers of varying sizes often bugs in form of buffer ;; overflows and similar. ;; ;; A strong factor in this situation is of course the protocol which goes ;; over the wire. Depending on its design it might be difficult to estimate ;; the size of the input up front. This introduces more handling of message ;; buffers to accomodate for inputs of varying sizes. This is particularly ;; difficult in languages like C, where there is no bounds checking of array ;; accesses and where errors might go unnoticed for considerable amount of ;; time. ;; ;; To address these issues D. Bernstein developed the so called ;; [netstrings][net]. They are especially designed to allow easy construction ;; of the message buffers, easy and robust parsing. ;; ;; BitTorrent extended this to the [bencode][bc] protocol which also ;; includes ways to encode numbers and collections like lists or maps. ;; ;; *wire* is based on these ideas. ;; ;; [net]: http://cr.yp.to/proto/netstrings.txt ;; [bc]: http://wiki.theory.org/BitTorrentSpecification#Bencoding ;; ;; # Netstrings ;; ;; Now let's start with the basic netstrings. They consist of a byte count, ;; followed a colon and the binary data and a trailing comma. Examples: ;; ;; 13:Hello, World!, ;; 10:Guten Tag!, ;; 0:, ;; ;; The initial byte count allows to efficiently allocate a sufficiently ;; sized message buffer. The trailing comma serves as a hint to detect ;; incorrect netstrings. ;; ;; ## Low-level reading ;; ;; We will need some low-level reading helpers to read the bytes from ;; the input stream. These are `read-byte` as well as `read-bytes`. They ;; are split out, because doing such a simple task as reading a byte is ;; mild catastrophe in Java. So it would add some clutter to the algorithm ;; `read-netstring`. ;; ;; On the other hand they might be also useful elsewhere. ;; ;; To remove some magic numbers from the code below. (def #^{:const true} i 105) (def #^{:const true} l 108) (def #^{:const true} d 100) (def #^{:const true} comma 44) (def #^{:const true} minus 45) ;; These two are only used boxed. So we keep them extra here. (def e 101) (def colon 58) (defn #^{:private true} read-byte #^long [#^InputStream input] (let [c (.read input)] (when (neg? c) (throw (EOFException. "Invalid netstring. Unexpected end of input."))) ;; Here we have a quirk for example. `.read` returns -1 on end of ;; input. However the Java `Byte` has only a range from -128 to 127. ;; How does the fit together? ;; ;; The whole thing is shifted. `.read` actually returns an int ;; between zero and 255. Everything below the value 128 stands ;; for itself. But larger values are actually negative byte values. ;; ;; So we have to do some translation here. `Byte/byteValue` would ;; do that for us, but we want to avoid boxing here. (if (< 127 c) (- c 256) c))) (defn #^{:private true :tag "[B"} read-bytes #^Object [#^InputStream input n] (let [content (byte-array n)] (loop [offset (int 0) len (int n)] (let [result (.read input content offset len)] (when (neg? result) (throw (EOFException. "Invalid netstring. Less data available than expected."))) (when (not= result len) (recur (+ offset result) (- len result))))) content)) ;; `read-long` is used for reading integers from the stream as well ;; as the byte count prefixes of byte strings. The delimiter is \: ;; for byte count prefixes and \e for integers. (defn #^{:private true} read-long #^long [#^InputStream input delim] (loop [n (long 0)] ;; We read repeatedly a byte from the input… (let [b (read-byte input)] ;; …and stop at the delimiter. (cond (= b minus) (- (read-long input delim)) (= b delim) n :else (recur (+ (* n (long 10)) (- (long b) (long 48)))))))) ;; ## Reading a netstring ;; ;; Let's dive straight into reading a netstring from an `InputStream`. ;; ;; For convenience we split the function into two subfunctions. The ;; public `read-netstring` is the normal entry point, which also checks ;; for the trailing comma after reading the payload data with the ;; private `read-netstring*`. ;; ;; The reason we need the less strict `read-netstring*` is that with ;; bencode we don't have a trailing comma. So a check would not be ;; beneficial here. ;; ;; However the consumer doesn't have to care. `read-netstring` as ;; well as `read-bencode` provide the public entry points, which do ;; the right thing. Although they both may reference the `read-netstring*` ;; underneath. ;; ;; With this in mind we define the inner helper function first. (declare #^"[B" string>payload #^String stringpayload` and `stringpayload [#^String s] (.getBytes s "UTF-8")) (defn #^{:private true :tag String} stringpayload (str (alength content)))) (.write (int colon)) (.write content))) (defn write-netstring "Write the given binary data to the output stream in form of a classic netstring." [#^OutputStream output content] (doto output (write-netstring* content) (.write (int comma)))) ;; # Bencode ;; ;; However most of the time we don't want to send simple blobs of data ;; back and forth. The data sent between the communication peers usually ;; have some structure, which has to be carried along the way to the ;; other side. Here [bencode][bc] come into play. ;; ;; Bencode defines additionally to netstrings easily parseable structures ;; for lists, maps and numbers. It allows to communicate information ;; about the data structure to the peer on the other side. ;; ;; ## Tokens ;; ;; The data is encoded in tokens in bencode. There are several types of ;; tokens: ;; ;; * A netstring without trailing comma for string data. ;; * A tag specifiyng the type of the following tokens. ;; The tag may be one of these: ;; * `\i` to encode integers. ;; * `\l` to encode lists of items. ;; * `\d` to encode maps of item pairs. ;; * `\e` to end the a previously started tag. ;; ;; ## Reading bencode ;; ;; Reading bencode encoded data is basically parsing a stream of tokens ;; from the input. Hence we need a read-token helper which allows to ;; retrieve the next token. (defn #^{:private true} read-token [#^PushbackInputStream input] (let [ch (read-byte input)] (cond (= (long e) ch) nil (= i ch) :integer (= l ch) :list (= d ch) :map :else (do (.unread input (int ch)) (read-netstring* input))))) ;; To read the bencode encoded data we walk a long the sequence of tokens ;; and act according to the found tags. (declare read-integer read-list read-map) (defn read-bencode "Read bencode token from the input stream." [input] (let [token (read-token input)] (case token :integer (read-integer input) :list (read-list input) :map (read-map input) token))) ;; Of course integers and the collection types are have to treated specially. ;; ;; Integers for example consist of a sequence of decimal digits. (defn #^{:private true} read-integer [input] (read-long input e)) ;; *Note:* integers are an ugly special case, which cannot be ;; handled with `read-token` or `read-netstring*`. ;; ;; Lists are just a sequence of other tokens. (declare token-seq) (defn #^{:private true} read-list [input] (vec (token-seq input))) ;; Maps are sequences of key/value pairs. The keys are always ;; decoded into strings. The values are kept as is. (defn #^{:private true} read-map [input] (->> (token-seq input) (partition 2) (map (fn [[k v]] [(string> #(read-bencode input) repeatedly (take-while identity))) ;; ## Writing bencode ;; ;; Writing bencode is similar easy as reading it. The main entry point ;; takes a string, map, sequence or integer and writes it according to ;; the rules to the given OutputStream. (defmulti write-bencode "Write the given thing to the output stream. “Thing” means here a string, map, sequence or integer. Alternatively an ByteArray may be provided whose contents are written as a bytestring. Similar the contents of a given InputStream are written as a byte string. Named things (symbols or keywords) are written in the form 'namespace/name'." (fn [_output thing] (cond (instance? (RT/classForName "[B") thing) :bytes (instance? InputStream thing) :input-stream (integer? thing) :integer (string? thing) :string (symbol? thing) :named (keyword? thing) :named (map? thing) :map (or (nil? thing) (coll? thing) (.isArray (class thing))) :list :else (type thing)))) (defmethod write-bencode :default [output x] (throw (IllegalArgumentException. (str "Cannot write value of type " (class x))))) ;; The following methods should be pretty straight-forward. ;; ;; The easiest case is of course when we already have a byte array. ;; We can simply pass it on to the underlying machinery. (defmethod write-bencode :bytes [output bytes] (write-netstring* output bytes)) ;; For strings we simply write the string as a netstring without ;; trailing comma after encoding the string as UTF-8 bytes. (defmethod write-bencode :string [output string] (write-netstring* output (string>payload string))) ;; Streaming does not really work, since we need to know the ;; number of bytes to write upfront. So we read in everything ;; for InputStreams and pass on the byte array. (defmethod write-bencode :input-stream [output stream] (let [bytes (ByteArrayOutputStream.)] (io/copy stream bytes) (write-netstring* output (.toByteArray bytes)))) ;; Integers are again the ugly special case. (defmethod write-bencode :integer [#^OutputStream output n] (doto output (.write (int i)) (.write (string>payload (str n))) (.write (int e)))) ;; Symbols and keywords are converted to a string of the ;; form 'namespace/name' or just 'name' in case its not ;; qualified. We do not add colons for keywords since the ;; other side might not have the notion of keywords. (defmethod write-bencode :named [output thing] (let [nspace (namespace thing) name (name thing)] (->> (str (when nspace (str nspace "/")) name) string>payload (write-netstring* output)))) ;; Lists as well as maps work recursively to print their elements. (defmethod write-bencode :list [#^OutputStream output lst] (.write output (int l)) (doseq [elt lst] (write-bencode output elt)) (.write output (int e))) ;; However, maps are a bit special because their keys are sorted ;; lexicographically based on their byte string represantation. (declare lexicographically) (defmethod write-bencode :map [#^OutputStream output m] (let [translation (into {} (map (juxt string>payload identity) (keys m))) key-strings (sort lexicographically (keys translation)) >value (comp m translation)] (.write output (int d)) (doseq [k key-strings] (write-netstring* output k) (write-bencode output (>value k))) (.write output (int e)))) ;; However, since byte arrays are not `Comparable` we need a custom ;; comparator which we can feed to `sort`. (defn #^{:private true} lexicographically [#^"[B" a #^"[B" b] (let [alen (alength a) blen (alength b) len (min alen blen)] (loop [i 0] (if (== i len) (- alen blen) (let [x (- (int (aget a i)) (int (aget b i)))] (if (zero? x) (recur (inc i)) x)))))) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/cmdline.clj000066400000000000000000000063561307046336100275540ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns #^{:doc "A proof-of-concept command-line client for nREPL. Please see e.g. reply for a proper command-line nREPL client @ https://github.com/trptcolin/reply/" :author "Chas Emerick"} clojure.tools.nrepl.cmdline (:require [clojure.tools.nrepl :as repl] [clojure.tools.nrepl.transport :as transport]) (:use (clojure.tools.nrepl [server :only (start-server)] [ack :only (send-ack)]))) (defn- ensure-newline [s] (if (= "\n" (last s)) s (str s \newline))) (def colored-output {:err #(binding [*out* *err*] (print "\033[31m") (print %) (print "\033[m") (flush)) :out print :value (fn [x] (print "\033[34m") (print x) (print "\033[m") (flush))}) (defn- run-repl ([port] (run-repl port nil)) ([port {:keys [prompt err out value] :or {prompt #(print (str % "=> ")) err println out println value println}}] (let [transport (repl/connect :host "localhost" :port port) client (repl/client-session (repl/client transport Long/MAX_VALUE)) ns (atom "user") {:keys [major minor incremental qualifier]} *clojure-version*] (println "network-repl") (println (str "Clojure " (clojure-version))) (loop [] (prompt @ns) (flush) (doseq [res (repl/message client {:op "eval" :code (pr-str (read))})] (when (:value res) (value (:value res))) (when (:out res) (out (:out res))) (when (:err res) (err (:err res))) (when (:ns res) (reset! ns (:ns res)))) (recur))))) (def #^{:private true} unary-options #{"--interactive" "--color"}) (defn- split-args [args] (loop [[arg & rem-args :as args] args options {}] (if-not (and arg (re-matches #"--.*" arg)) [options args] (if (unary-options arg) (recur rem-args (assoc options arg true)) (recur (rest rem-args) (assoc options arg (first rem-args))))))) (defn -main [& args] (let [[options args] (split-args args) server (start-server :port (Integer/parseInt (or (options "--port") "0"))) ^java.net.ServerSocket ssocket (:ss @server)] (when-let [ack-port (options "--ack")] (binding [*out* *err*] (println (format "ack'ing my port %d to other server running on port %s" (.getLocalPort ssocket) ack-port) (:status (send-ack (.getLocalPort ssocket) (Integer/parseInt ack-port)))))) (if (options "--interactive") (run-repl (.getLocalPort ssocket) (when (options "--color") colored-output)) ; need to hold process open with a non-daemon thread -- this should end up being super-temporary (Thread/sleep Long/MAX_VALUE)))) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/helpers.clj000066400000000000000000000044171307046336100275770ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns ^{:author "Chas Emerick"} clojure.tools.nrepl.helpers (:require [clojure.tools.nrepl.middleware.load-file :as load-file]) (:import (java.io File StringReader))) (defn load-file-command "(If it is available, sending clojure.tools.nrepl.middleware.load-file compatible messages is far preferable.) Returns a string expression that can be sent to an nREPL session to load the Clojure code in given local file in the remote REPL's environment, preserving debug information (e.g. line numbers, etc). Typical usage: (nrepl-client-fn {:op \"eval\" :code (load-file-command \"/path/to/clojure/file.clj\")}) If appropriate, the source path from which the code is being loaded may be provided as well (suitably trimming the file's path to a relative one when loaded). The 3-arg variation of this function expects the full source of the file to be loaded, the source-root-relative path of the source file, and the name of the file. e.g.: (load-file-command \"…code here…\" \"some/ns/name/file.clj\" \"file.clj\")" ([f] (load-file-command f nil)) ([f source-root] (let [^String abspath (if (string? f) f (.getAbsolutePath ^File f)) source-root (cond (nil? source-root) "" (string? source-root) source-root (instance? File source-root) (.getAbsolutePath ^File source-root))] (load-file-command (slurp abspath :encoding "UTF-8") (if (and (seq source-root) (.startsWith abspath source-root)) (-> abspath (.substring (count source-root)) (.replaceAll "^[/\\\\]" "")) abspath) (-> abspath File. .getName)))) ([code file-path file-name] (load-file/load-file-code code file-path file-name)))tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/middleware.clj000066400000000000000000000237441307046336100302560ustar00rootroot00000000000000(ns clojure.tools.nrepl.middleware (:require clojure.tools.nrepl [clojure.tools.nrepl.transport :as transport] [clojure.tools.nrepl.misc :as misc] [clojure.set :as set]) (:refer-clojure :exclude (comparator ex-info))) ;; so much backport (def ^:private ex-info (or (resolve 'clojure.core/ex-info) (fn [^String msg data] (proxy [Exception clojure.lang.IDeref] [msg] (deref [] data))))) (defn- var-name [^clojure.lang.Var v] (str (.ns v) \/ (.sym v))) (defn- wrap-conj-descriptor [descriptor-map h] (fn [{:keys [op descriptors] :as msg}] (h (if-not (= op "describe") msg (assoc msg :descriptors (merge descriptor-map descriptors)))))) (defn set-descriptor! "Sets the given [descriptor] map as the ::descriptor metadata on the provided [middleware-var], after assoc'ing in the var's fully-qualified name as the descriptor's \"implemented-by\" value." [middleware-var descriptor] (let [descriptor (-> descriptor (assoc :implemented-by (-> middleware-var var-name symbol)) (update-in [:expects] (fnil conj #{}) "describe"))] (alter-meta! middleware-var assoc ::descriptor descriptor) (alter-var-root middleware-var #(comp (partial wrap-conj-descriptor {middleware-var descriptor}) %)))) (defn- safe-version [m] (into {} (filter (fn [[_ v]] (or (number? v) (string? v))) m))) (defn- java-version [] (let [version-string (System/getProperty "java.version") version-seq (re-seq #"\d+" version-string) version-map (if (<= 3 (count version-seq)) (zipmap [:major :minor :incremental :update] version-seq) {})] (assoc version-map :version-string version-string))) (defn wrap-describe [h] (fn [{:keys [op descriptors verbose? transport] :as msg}] (if (= op "describe") (transport/send transport (misc/response-for msg (merge (when-let [aux (reduce (fn [aux {:keys [describe-fn]}] (if describe-fn (merge aux (describe-fn msg)) aux)) nil (vals descriptors))] {:aux aux}) {:ops (let [ops (apply merge (map :handles (vals descriptors)))] (if verbose? ops (zipmap (keys ops) (repeat {})))) :versions {:nrepl (safe-version clojure.tools.nrepl/version) :clojure (safe-version (assoc *clojure-version* :version-string (clojure-version))) :java (safe-version (java-version))} :status :done}))) (h msg)))) (set-descriptor! #'wrap-describe {:handles {"describe" {:doc "Produce a machine- and human-readable directory and documentation for the operations supported by an nREPL endpoint." :requires {} :optional {"verbose?" "Include informational detail for each \"op\"eration in the return message."} :returns {"ops" "Map of \"op\"erations supported by this nREPL endpoint" "versions" "Map containing version maps (like *clojure-version*, e.g. major, minor, incremental, and qualifier keys) for values, component names as keys. Common keys include \"nrepl\" and \"clojure\"." "aux" "Map of auxilliary data contributed by all of the active nREPL middleware via :describe-fn functions in their descriptors."}}}}) ; eliminate implicit expectation of "describe" handler; this is the only ; special case introduced by the conj'ing of :expects "describe" by set-descriptor! (alter-meta! #'wrap-describe update-in [::descriptor :expects] disj "describe") (defn- dependencies [set start dir] (let [ops (start dir) deps (set/select (comp seq (partial set/intersection ops) :handles) set)] (when (deps start) (throw (IllegalArgumentException. (format "Middleware %s depends upon itself via %s" (:implemented-by start) dir)))) (concat ops (mapcat #(dependencies set % dir) deps)))) (defn- comparator [{a-requires :requires a-expects :expects a-handles :handles} {b-requires :requires b-expects :expects b-handles :handles}] (or (->> (into {} [[[a-requires b-handles] -1] [[a-expects b-handles] 1] [[b-requires a-handles] 1] [[b-expects a-handles] -1]]) (map (fn [[sets ret]] (and (seq (apply set/intersection sets)) ret))) (some #{-1 1})) 0)) (defn- extend-deps [middlewares] (let [descriptor #(-> % meta ::descriptor) middlewares (concat middlewares (->> (map descriptor middlewares) (mapcat (juxt :expects :requires)) (mapcat identity) (filter var?)))] (doseq [m (remove descriptor middlewares)] (binding [*out* *err*] (printf "[WARNING] No nREPL middleware descriptor in metadata of %s, see clojure.tools.middleware/set-descriptor!" m) (println))) (let [middlewares (set (for [m middlewares] (-> (descriptor m) ; only conj'ing m here to support direct reference to ; middleware dependencies in :expects and :requires, ; e.g. interruptable-eval's dep on ; clojure.tools.nrepl.middleware.pr-values/pr-values (update-in [:handles] (comp set #(conj % m) keys)) (assoc :implemented-by m))))] (set (for [m middlewares] (reduce #(update-in % [%2] into (dependencies middlewares % %2)) m #{:expects :requires})))))) (defn- topologically-sort "Topologically sorts the given middlewares according to the comparator, with the added huristic that any middlewares that have no dependencies will be sorted toward the end." [comparator stack] (let [stack (vec stack) ;; using indexes into the above vector as the vertices in the ;; graph algorithm, will translate back into middlewares at ;; the end. vertices (range (count stack)) edges (for [i1 vertices i2 (range i1) :let [x (comparator (stack i1) (stack i2))] :when (not= 0 x)] (if (neg? x) [i1 i2] [i2 i1])) ;; the trivial vertices have no connections, and we pull them ;; out here so we can make sure they get put on the end trivial-vertices (remove (set (apply concat edges)) vertices)] (loop [sorted-vertices [] remaining-edges edges remaining-vertices (remove (set trivial-vertices) vertices)] (if (seq remaining-vertices) (let [non-initials (->> remaining-edges (map second) (set)) next-vertex (->> remaining-vertices (remove non-initials) (first))] (if next-vertex (recur (conj sorted-vertices next-vertex) (remove #((set %) next-vertex) remaining-edges) (remove #{next-vertex} remaining-vertices)) ;; Cycle detected! Have to actually assemble a cycle so we ;; can throw a useful error. (let [start (first remaining-vertices) step (into {} remaining-edges) cycle (->> (iterate step start) (rest) (take-while (complement #{start})) (cons start)) data {:cycle (map stack cycle)}] (throw (ex-info "Unable to satisfy nrepl middleware ordering requirements!" data))))) (map stack (concat sorted-vertices trivial-vertices)))))) (defn linearize-middleware-stack [middlewares] (->> middlewares extend-deps (topologically-sort comparator) (map :implemented-by))) ;;; documentation utilities ;;; ; oh, kill me now (defn- markdown-escape [^String s] (.replaceAll s "([*_])" "\\\\$1")) (defn- message-slot-markdown [msg-slot-docs] (apply str (for [[k v] msg-slot-docs] (format "* `%s` %s\n" (pr-str k) (markdown-escape v))))) (defn- describe-markdown "Given a message containing the response to a verbose :describe message, generates a markdown string conveying the information therein, suitable for use in e.g. wiki pages, github, etc. (This is currently private because markdown conversion surely shouldn't be part of the API here...?)" [{:keys [ops versions]}] (apply str "# Supported nREPL operations generated from a verbose 'describe' response (nREPL v" (:version-string clojure.tools.nrepl/version) ")\n\n## Operations" (for [[op {:keys [doc optional requires returns]}] ops] (str "\n\n### `" (pr-str op) "`\n\n" (markdown-escape doc) "\n\n" "###### Required parameters\n\n" (message-slot-markdown requires) "\n\n###### Optional parameters\n\n" (message-slot-markdown optional) "\n\n###### Returns\n\n" (message-slot-markdown returns))))) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/middleware/000077500000000000000000000000001307046336100275525ustar00rootroot00000000000000interruptible_eval.clj000066400000000000000000000314401307046336100340660ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/middleware(ns ^{:author "Chas Emerick"} clojure.tools.nrepl.middleware.interruptible-eval (:require [clojure.tools.nrepl.transport :as t] clojure.tools.nrepl.middleware.pr-values clojure.main) (:use [clojure.tools.nrepl.misc :only (response-for returning)] [clojure.tools.nrepl.middleware :only (set-descriptor!)]) (:import clojure.lang.LineNumberingPushbackReader (java.io FilterReader LineNumberReader StringReader Writer) java.lang.reflect.Field java.util.concurrent.atomic.AtomicLong (java.util.concurrent Executor BlockingQueue LinkedBlockingQueue ThreadFactory SynchronousQueue TimeUnit ThreadPoolExecutor))) (def ^:private reader-conditionals? (boolean (resolve 'clojure.core/reader-conditional))) (def ^{:dynamic true :doc "The message currently being evaluated."} *msg* nil) (def ^{:dynamic true :doc "Function returning the evaluation of its argument."} *eval* nil) (defn- capture-thread-bindings "Capture thread bindings, excluding nrepl implementation vars." [] (dissoc (get-thread-bindings) #'*msg* #'*eval*)) (defn- set-line! [^LineNumberingPushbackReader reader line] (-> FilterReader ^Field (.getDeclaredField "in") (doto (.setAccessible true)) ^LineNumberReader (.get reader) (.setLineNumber line))) (defn- set-column! [^LineNumberingPushbackReader reader column] (when-let [field (->> LineNumberingPushbackReader (.getDeclaredFields) (filter #(= "_columnNumber" (.getName ^Field %))) first)] (-> ^Field field (doto (.setAccessible true)) (.set reader column)))) (defn- source-logging-pushback-reader [code line column] (let [reader (LineNumberingPushbackReader. (StringReader. code))] (when line (set-line! reader (int (dec line)))) (when column (set-column! reader (int column))) reader)) (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 session eval file line column] :as msg}] (let [explicit-ns-binding (when-let [ns (and ns (-> ns symbol find-ns))] {#'*ns* ns}) original-ns (bindings #'*ns*) maybe-restore-original-ns (fn [bindings] (if-not explicit-ns-binding bindings (assoc bindings #'*ns* original-ns))) file (or file (get bindings #'*file*)) bindings (atom (merge bindings explicit-ns-binding {#'*file* file})) session (or session (atom nil)) out (@bindings #'*out*) err (@bindings #'*err*)] (if (and ns (not explicit-ns-binding)) (t/send transport (response-for msg {:status #{:error :namespace-not-found :done}})) (with-bindings @bindings (try (clojure.main/repl :eval (if eval (find-var (symbol eval)) clojure.core/eval) ;; clojure.main/repl paves over certain vars even if they're already thread-bound :init #(do (set! *compile-path* (@bindings #'*compile-path*)) (set! *1 (@bindings #'*1)) (set! *2 (@bindings #'*2)) (set! *3 (@bindings #'*3)) (set! *e (@bindings #'*e))) :read (if (string? code) (let [reader (source-logging-pushback-reader code line column)] (if reader-conditionals? #(read {:read-cond :allow :eof %2} reader) #(read reader false %2))) (let [code (.iterator ^Iterable code)] #(or (and (.hasNext code) (.next code)) %2))) :prompt (fn []) :need-prompt (constantly false) ; TODO pretty-print? :print (fn [v] (reset! bindings (assoc (capture-thread-bindings) #'*3 *2 #'*2 *1 #'*1 v)) (.flush ^Writer err) (.flush ^Writer out) (reset! session (maybe-restore-original-ns @bindings)) (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 (capture-thread-bindings) #'*e e)) (reset! session (maybe-restore-original-ns @bindings)) (t/send transport (response-for msg {:status :eval-error :ex (-> e class str) :root-ex (-> root-ex class str)})) (clojure.main/repl-caught e))))) (finally (.flush ^Writer out) (.flush ^Writer err))))) (maybe-restore-original-ns @bindings))) (defn- configure-thread-factory "Returns a new ThreadFactory for the given session. This implementation generates daemon threads, with names that include the session id." [] (let [session-thread-counter (AtomicLong. 0)] (reify ThreadFactory (newThread [_ runnable] (doto (Thread. runnable (format "nREPL-worker-%s" (.getAndIncrement session-thread-counter))) (.setDaemon true)))))) (def ^{:private true} jdk6? (try (Class/forName "java.util.ServiceLoader") true (catch ClassNotFoundException e false))) ; this is essentially the same as Executors.newCachedThreadPool, except ; for the JDK 5/6 fix described below (defn- configure-executor "Returns a ThreadPoolExecutor, configured (by default) to have no core threads, use an unbounded queue, create only daemon threads, and allow unused threads to expire after 30s." [& {:keys [keep-alive queue thread-factory] :or {keep-alive 30000 queue (SynchronousQueue.)}}] (let [^ThreadFactory thread-factory (or thread-factory (configure-thread-factory))] ; ThreadPoolExecutor in JDK5 *will not run* submitted jobs if the core pool size is zero and ; the queue has not yet rejected a job (see http://kirkwylie.blogspot.com/2008/10/java5-vs-java6-threadpoolexecutor.html) (ThreadPoolExecutor. (if jdk6? 0 1) Integer/MAX_VALUE (long 30000) TimeUnit/MILLISECONDS ^BlockingQueue queue thread-factory))) (def default-executor (delay (configure-executor))) ; A little mini-agent implementation. Needed because agents cannot be used to host REPL ; evaluation: http://dev.clojure.org/jira/browse/NREPL-17 (defn- prep-session [session] (locking session (returning session (when-not (-> session meta :queue) (alter-meta! session assoc :queue (atom clojure.lang.PersistentQueue/EMPTY)))))) (declare run-next) (defn- run-next* [session ^Executor executor] (let [qa (-> session meta :queue)] (loop [] (let [q @qa qn (pop q)] (if-not (compare-and-set! qa q qn) (recur) (when (seq qn) (.execute executor (run-next session executor (peek qn))))))))) (defn- run-next [session executor f] #(try (f) (finally (run-next* session executor)))) (defn queue-eval "Queues the function for the given session." [session ^Executor executor f] (let [qa (-> session prep-session meta :queue)] (loop [] (let [q @qa] (if-not (compare-and-set! qa q (conj q f)) (recur) (when (empty? q) (.execute executor (run-next session executor f)))))))) (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 & configuration] (let [executor (:executor configuration @default-executor)] (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})) (queue-eval session executor (fn [] (alter-meta! session assoc :thread (Thread/currentThread) :eval-msg msg) (binding [*msg* msg] (evaluate @session msg) (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 ; notify of the interrupted status before we .stop the thread so ; it is received before the standard :done status (thereby ensuring ; that is stays within the scope of a clojure.tools.nrepl/message seq (t/send transport {:status #{:interrupted} :id (:id eval-msg) :session id}) (.stop thread) (t/send transport (response-for msg :status #{:done})))) (t/send transport (response-for msg :status #{:error :interrupt-id-mismatch :done})))) (h msg))))) (set-descriptor! #'interruptible-eval {:requires #{"clone" "close" #'clojure.tools.nrepl.middleware.pr-values/pr-values} :expects #{} :handles {"eval" {:doc "Evaluates code." :requires {"code" "The code to be evaluated." "session" "The ID of the session within which to evaluate the code."} :optional {"id" "An opaque message ID that will be included in responses related to the evaluation, and which may be used to restrict the scope of a later \"interrupt\" operation." "eval" "A fully-qualified symbol naming a var whose function value will be used to evaluate [code], instead of `clojure.core/eval` (the default)." "file" "The path to the file containing [code]. `clojure.core/*file*` will be bound to this." "line" "The line number in [file] at which [code] starts." "column" "The column number in [file] at which [code] starts."} :returns {"ns" "*ns*, after successful evaluation of `code`." "values" "The result of evaluating `code`, often `read`able. This printing is provided by the `pr-values` middleware, and could theoretically be customized. Superseded by `ex` and `root-ex` if an exception occurs during evaluation." "ex" "The type of exception thrown, if any. If present, then `values` will be absent." "root-ex" "The type of the root exception thrown, if any. If present, then `values` will be absent."}} "interrupt" {:doc "Attempts to interrupt some code evaluation." :requires {"session" "The ID of the session used to start the evaluation to be interrupted."} :optional {"interrupt-id" "The opaque message ID sent with the original \"eval\" request."} :returns {"status" "'interrupted' if an evaluation was identified and interruption will be attempted 'session-idle' if the session is not currently evaluating any code 'interrupt-id-mismatch' if the session is currently evaluating code sent using a different ID than specified by the \"interrupt-id\" value "}}}}) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/middleware/load_file.clj000066400000000000000000000116211307046336100321630ustar00rootroot00000000000000(ns ^{:author "Chas Emerick"} clojure.tools.nrepl.middleware.load-file (:require [clojure.tools.nrepl.middleware.interruptible-eval :as eval] [clojure.tools.nrepl.transport :as t]) (:import clojure.tools.nrepl.transport.Transport) (:use [clojure.tools.nrepl.middleware :as middleware :only (set-descriptor!)])) ; need to hold file contents "out of band" so as to avoid JVM method ; size limitations (cannot eval an expression larger than some size ; [64k?]), so the naive approach of just interpolating file contents ; into an expression to be evaluated doesn't work ; see http://code.google.com/p/counterclockwise/issues/detail?id=429 ; and http://groups.google.com/group/clojure/browse_thread/thread/f54044da06b9939f (defonce ^{:private true :doc "An atom that temporarily holds the contents of files to be loaded."} file-contents (atom {})) (defn- load-large-file-code "A variant of `load-file-code` that returns an expression that will only work if evaluated within the same process where it was called. Here to work around the JVM method size limit so that (by default, for those tools using the load-file middleware) loading files of any size will work when the nREPL server is running remotely or locally." [file file-path file-name] ; mini TTL impl so that any code orphaned by errors that occur ; between here and the evaluation of the Compiler/load expression ; below are cleaned up on subsequent loads (let [t (System/currentTimeMillis) file-key ^{:t t} [file-path (gensym)]] (swap! file-contents (fn [file-contents] (let [expired-keys (filter (comp #(and % (< 10000 (- (System/currentTimeMillis) %))) :t meta) (keys file-contents))] (assoc (apply dissoc file-contents expired-keys) file-key file)))) (binding [*print-length* nil *print-level* nil] (pr-str `(try (clojure.lang.Compiler/load (java.io.StringReader. (@@(var file-contents) '~file-key)) ~file-path ~file-name) (finally (swap! @(var file-contents) dissoc '~file-key))))))) (defn ^{:dynamic true} load-file-code "Given the contents of a file, its _source-path-relative_ path, and its filename, returns a string of code containing a single expression that, when evaluated, will load those contents with appropriate filename references and line numbers in metadata, etc. Note that because a single expression is produced, very large file loads will fail due to the JVM method size limitation. In such cases, see `load-large-file-code'`." [file file-path file-name] (apply format "(clojure.lang.Compiler/load (java.io.StringReader. %s) %s %s)" (map (fn [item] (binding [*print-length* nil *print-level* nil] (pr-str item))) [file file-path file-name]))) (defn wrap-load-file "Middleware that evaluates a file's contents, as per load-file, but with all data supplied in the sent message (i.e. safe for use with remote REPL environments). This middleware depends on the availability of an :op \"eval\" middleware below it (such as interruptible-eval)." [h] (fn [{:keys [op file file-name file-path transport] :as msg}] (if (not= op "load-file") (h msg) (h (assoc (dissoc msg :file :file-name :file-path) :op "eval" :code ((if (thread-bound? #'load-file-code) load-file-code load-large-file-code) file file-path file-name) :transport (reify Transport (recv [this] (.recv transport)) (recv [this timeout] (.recv transport timeout)) (send [this resp] ; *ns* is always 'user' after loading a file, so ; *remove it to avoid confusing tools that assume any ; *:ns always reports *ns* (.send transport (dissoc resp :ns)) this))))))) (set-descriptor! #'wrap-load-file {:requires #{} :expects #{"eval"} :handles {"load-file" {:doc "Loads a body of code, using supplied path and filename info to set source file and line number metadata. Delegates to underlying \"eval\" middleware/handler." :requires {"file" "Full contents of a file of code."} :optional {"file-path" "Source-path-relative path of the source file, e.g. clojure/java/io.clj" "file-name" "Name of source file, e.g. io.clj"} :returns (-> (meta #'eval/interruptible-eval) ::middleware/descriptor :handles (get "eval") :returns)}}}) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/middleware/pr_values.clj000066400000000000000000000035221307046336100322460ustar00rootroot00000000000000 (ns ^{:author "Chas Emerick"} clojure.tools.nrepl.middleware.pr-values (:require [clojure.tools.nrepl.transport :as t]) (:use [clojure.tools.nrepl.middleware :only (set-descriptor!)]) (: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. If :value is already a string, and a sent message's :printed-value slot contains any truthy value, then :value will not be re-printed. This allows evaluation contexts to produce printed results in :value if they so choose, and opt out of the printing here." [h] (fn [{:keys [op ^Transport transport] :as msg}] (h (assoc msg :transport (reify Transport (recv [this] (.recv transport)) (recv [this timeout] (.recv transport timeout)) (send [this {:keys [printed-value value] :as resp}] (.send transport (if (and printed-value (string? value)) (dissoc resp :printed-value) (if-let [[_ v] (find resp :value)] (assoc resp :value (let [repr (java.io.StringWriter.)] (if *print-dup* (print-dup v repr) (print-method v repr)) (str repr))) resp))) this)))))) (set-descriptor! #'pr-values {:requires #{} :expects #{} :handles {}}) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/middleware/session.clj000066400000000000000000000272101307046336100317310ustar00rootroot00000000000000 (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*)] [clojure.tools.nrepl.middleware :only (set-descriptor!)]) (: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 java.util.concurrent.LinkedBlockingQueue)) (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... (def ^{:dynamic true :private true} *out-limit* 1024) (def ^{:dynamic true :private true} *skipping-eol* false) (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 ^Integer off ^Integer len]] (locking buf (cond (number? x) (.append buf (char x)) (not off) (.append buf x) ; the CharSequence overload of append takes an *end* idx, not length! (instance? CharSequence x) (.append buf ^CharSequence x (int off) (int (+ len off))) :else (.append buf ^chars x off len)) (when (<= *out-limit* (.length buf)) (.flush ^Writer this)))) (flush [] (let [text (locking buf (let [text (str buf)] (.setLength buf 0) text))] (when (pos? (count text)) (t/send (or (:transport *msg*) transport) (response-for *msg* :session session-id channel-type text)))))) true))) (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 [input-queue (LinkedBlockingQueue.) request-input (fn [] (cond (> (.size input-queue) 0) (.take input-queue) *skipping-eol* nil :else (do (t/send transport (response-for *msg* :session session-id :status :need-input)) (.take input-queue)))) do-read (fn [buf off len] (locking input-queue (loop [i off] (cond (>= i (+ off len)) (+ off len) (.peek input-queue) (do (aset-char buf i (char (.take input-queue))) (recur (inc i))) :else i)))) reader (LineNumberingPushbackReader. (proxy [Reader] [] (close [] (.clear input-queue)) (read ([] (let [^Reader this this] (proxy-super read))) ([x] (let [^Reader this this] (if (instance? java.nio.CharBuffer x) (proxy-super read ^java.nio.CharBuffer x) (proxy-super read ^chars x)))) ([^chars buf off len] (if (zero? len) -1 (let [first-character (request-input)] (if (or (nil? first-character) (= first-character -1)) -1 (do (aset-char buf off (char first-character)) (- (do-read buf (inc off) (dec len)) off)))))))))] {:input-queue input-queue :stdin-reader reader})) (defn- create-session "Returns a new atom 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) {:keys [input-queue stdin-reader]} (session-in id transport)] (binding [*out* out *err* (session-out :err id transport) *in* stdin-reader *ns* (create-ns 'user) *out-limit* (or (baseline-bindings #'*out-limit*) 1024) ; 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] ; nrepl.server happens to use agents for connection dispatch ; don't capture that *agent* binding for userland REPL sessions (atom (merge baseline-bindings (dissoc (get-thread-bindings) #'*agent*)) :meta {:id id :stdin-reader stdin-reader :input-queue input-queue})))))) (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 out-limit] :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)] ;; TODO yak, this is ugly; need to cleanly thread out-limit through to ;; session-out without abusing a dynamic var ;; (there's no reason to allow a connected client to fark around with ;; a session-out's "buffer") (when out-limit (swap! the-session assoc #'*out-limit* out-limit)) (case op "clone" (register-session msg) "close" (close-session msg) "ls-sessions" (t/send transport (response-for msg :status :done :sessions (or (keys @sessions) []))) (h msg))))))) (set-descriptor! #'session {:requires #{} :expects #{} :describe-fn (fn [{:keys [session] :as describe-msg}] (when (and session (instance? clojure.lang.Atom session)) {:current-ns (-> @session (get #'*ns*) str)})) :handles {"close" {:doc "Closes the specified session." :requires {"session" "The ID of the session to be closed."} :optional {} :returns {}} "ls-sessions" {:doc "Lists the IDs of all active sessions." :requires {} :optional {} :returns {"sessions" "A list of all available session IDs."}} "clone" {:doc "Clones the current session, returning the ID of the newly-created session." :requires {} :optional {"session" "The ID of the session to be cloned; if not provided, a new session with default bindings is created, and mapped to the returned session ID."} :returns {"new-session" "The ID of the new session."}}}}) (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}] (cond (= op "eval") (let [in (-> (meta session) ^LineNumberingPushbackReader (:stdin-reader))] (binding [*skipping-eol* true] (clojure.main/skip-if-eol in)) (h msg)) (= op "stdin") (let [q (-> (meta session) ^LinkedBlockingQueue (:input-queue))] (if (empty? stdin) (.put q -1) (locking q (doseq [c stdin] (.put q c)))) (t/send transport (response-for msg :status :done))) :else (h msg)))) (set-descriptor! #'add-stdin {:requires #{#'session} :expects #{"eval"} :handles {"stdin" {:doc "Add content from the value of \"stdin\" to *in* in the current session." :requires {"stdin" "Content to add to *in*."} :optional {} :returns {"status" "A status of \"need-input\" will be sent if a session's *in* requires content in order to satisfy an attempted read operation."}}}}) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/misc.clj000066400000000000000000000042701307046336100270650ustar00rootroot00000000000000(ns ^{:doc "Misc utilities used in nREPL's implementation (potentially also useful for anyone extending it)." :author "Chas Emerick"} clojure.tools.nrepl.misc) (try (require 'clojure.tools.logging) (defmacro log [& args] `(clojure.tools.logging/error ~@args)) (catch Throwable t ;(println "clojure.tools.logging not available, falling back to stdout/err") (defn log [ex & msgs] (let [ex (when (instance? Throwable ex) ex) msgs (if ex msgs (cons ex msgs))] (binding [*out* *err*] (apply println "ERROR:" msgs) (when ex (.printStackTrace ^Throwable ex))))))) (defmacro returning "Executes `body`, returning `x`." [x & body] `(let [x# ~x] ~@body x#)) (defn uuid "Returns a new UUID string." [] (str (java.util.UUID/randomUUID))) (defn response-for "Returns a map containing the :session and :id from the \"request\" `msg` as well as all entries specified in `response-data`, which can be one or more maps (which will be merged), *or* key-value pairs. (response-for msg :status :done :value \"5\") (response-for msg {:status :interrupted}) 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." [{:keys [session id]} & response-data] {:pre [(seq response-data)]} (let [{:keys [status] :as response} (if (map? (first response-data)) (reduce merge response-data) (apply hash-map response-data)) response (if (not status) response (assoc response :status (if (coll? status) status #{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)))tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/server.clj000077500000000000000000000147071307046336100274510ustar00rootroot00000000000000(ns ^{:doc "Default server implementations" :author "Chas Emerick"} clojure.tools.nrepl.server (:require [clojure.tools.nrepl :as repl] (clojure.tools.nrepl [ack :as ack] [transport :as t] [middleware :as middleware]) (clojure.tools.nrepl.middleware interruptible-eval pr-values session load-file) clojure.pprint) (:use [clojure.tools.nrepl.misc :only (returning response-for log)]) (:import (java.net Socket ServerSocket InetSocketAddress InetAddress))) (defn handle* [msg handler transport] (try (handler (assoc msg :transport transport)) (catch Throwable t (log t "Unhandled REPL handler exception processing message" msg)))) (defn handle "Handles requests received via [transport] using [handler]. Returns nil when [recv] returns nil for the given transport." [handler transport] (when-let [msg (t/recv transport)] (future (handle* msg handler transport)) (recur handler transport))) (defn- safe-close [^java.io.Closeable x] (try (.close x) (catch java.io.IOException e (log e "Failed to close " x)))) (defn- accept-connection [{:keys [^ServerSocket server-socket open-transports transport greeting handler] :as server}] (when-not (.isClosed server-socket) (let [sock (.accept server-socket)] (future (let [transport (transport sock)] (try (swap! open-transports conj transport) (when greeting (greeting transport)) (handle handler transport) (finally (swap! open-transports disj transport) (safe-close transport))))) (future (accept-connection server))))) (defn stop-server "Stops a server started via `start-server`." [{:keys [open-transports ^ServerSocket server-socket] :as server}] (returning server (.close server-socket) (swap! open-transports #(reduce (fn [s t] ; should always be true for the socket server... (if (instance? java.io.Closeable t) (do (safe-close t) (disj s t)) s)) % %)))) (defn unknown-op "Sends an :unknown-op :error for the given message." [{:keys [op transport] :as msg}] (t/send transport (response-for msg :status #{:error :unknown-op :done} :op op))) (def default-middlewares [#'clojure.tools.nrepl.middleware/wrap-describe #'clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval #'clojure.tools.nrepl.middleware.load-file/wrap-load-file #'clojure.tools.nrepl.middleware.session/add-stdin #'clojure.tools.nrepl.middleware.session/session]) (defn default-handler "A default handler supporting interruptible evaluation, stdin, sessions, and readable representations of evaluated expressions via `pr`. Additional middlewares to mix into the default stack may be provided; these should all be values (usually vars) that have an nREPL middleware descriptor in their metadata (see clojure.tools.nrepl.middleware/set-descriptor!)." [& additional-middlewares] (let [stack (middleware/linearize-middleware-stack (concat default-middlewares additional-middlewares))] ((apply comp (reverse stack)) unknown-op))) ;; TODO #_(defn- output-subscriptions [h] (fn [{:keys [op sub unsub] :as msg}] (case op "sub" ;; TODO "unsub" (h msg)))) (defrecord Server [server-socket port open-transports transport greeting handler] java.io.Closeable (close [this] (stop-server this)) ;; TODO here for backward compat with 0.2.x; drop for 0.3.0; this is what's ;; causing the print-method silliness below clojure.lang.IDeref (deref [this] this)) (#'clojure.pprint/use-method clojure.pprint/simple-dispatch Server #'clojure.pprint/pprint-simple-default) (try ; IRecord not available in 1.2.0 (eval '(defmethod print-method Server [s w] ((get-method print-method clojure.lang.IRecord) s w))) (catch Throwable _)) (defn start-server "Starts a socket-based nREPL server. Configuration options include: * :port — defaults to 0, which autoselects an open port * :bind — bind address, by default \"::\" (falling back to \"localhost\" if \"::\" isn't resolved by the underlying network stack) * :handler — the nREPL message handler to use for each incoming connection; 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. * :ack-port — if specified, the port of an already-running server that will be connected to to inform of the new server's port. Useful only by Clojure tooling implementations. Returns a (map) handle to the server that is started, which may be stopped either via `stop-server`, (.close server), or automatically via `with-open`. The port that the server is open on is available in the :port slot of the server map (useful if the :port option is 0 or was left unspecified." [& {:keys [port bind transport-fn handler ack-port greeting-fn] :or {port 0}}] (let [bind-addr (if bind (InetSocketAddress. ^String bind ^Integer port) (let [local (InetSocketAddress. "::" port)] (if (.isUnresolved local) (InetSocketAddress. "localhost" port) local))) ss (doto (ServerSocket.) (.setReuseAddress true) (.bind bind-addr)) server (assoc (Server. ss (.getLocalPort ss) (atom #{}) (or transport-fn t/bencode) greeting-fn (or handler (default-handler))) ;; TODO here for backward compat with 0.2.x; drop eventually :ss ss)] (future (accept-connection server)) (when ack-port (ack/send-ack (:port server) ack-port)) server)) tools.nrepl-tools.nrepl-0.2.13/src/main/clojure/clojure/tools/nrepl/transport.clj000066400000000000000000000144701307046336100301710ustar00rootroot00000000000000 (ns ^{:author "Chas Emerick"} clojure.tools.nrepl.transport (:require [clojure.tools.nrepl.bencode :as be] [clojure.java.io :as io] (clojure walk set)) (:use [clojure.tools.nrepl.misc :only (returning uuid)]) (:refer-clojure :exclude (send)) (:import (java.io InputStream OutputStream PushbackInputStream PushbackReader IOException EOFException) (java.net Socket SocketException) (java.util.concurrent SynchronousQueue LinkedBlockingQueue BlockingQueue TimeUnit) clojure.lang.RT)) (defprotocol Transport "Defines the interface for a wire protocol implementation for use with nREPL." (recv [this] [this timeout] "Reads and returns the next message received. Will block. Should return nil the a message is not available after `timeout` ms or if the underlying channel has been closed.") (send [this msg] "Sends msg. Implementations should return the transport.")) (deftype FnTransport [recv-fn send-fn close] Transport ;; TODO this keywordization/stringification has no business being in FnTransport (send [this msg] (-> msg clojure.walk/stringify-keys send-fn) this) (recv [this] (.recv this Long/MAX_VALUE)) (recv [this timeout] (clojure.walk/keywordize-keys (recv-fn timeout))) java.io.Closeable (close [this] (close))) (defn fn-transport "Returns a Transport implementation that delegates its functionality to the 2 or 3 functions provided." ([read write] (fn-transport read write nil)) ([read write close] (let [read-queue (SynchronousQueue.) msg-pump (future (try (while true (.put read-queue (read))) (catch Throwable t (.put read-queue t))))] (FnTransport. (let [failure (atom nil)] #(if @failure (throw @failure) (let [msg (.poll read-queue % TimeUnit/MILLISECONDS)] (if (instance? Throwable msg) (do (reset! failure msg) (throw msg)) msg)))) write (fn [] (close) (future-cancel msg-pump)))))) (defmulti #^{:private true} > input (map (fn [[k v]] [k ( "))) session-id (atom nil) read-msg #(let [code (read r)] (merge {:op "eval" :code [code] :ns @cns :id (str "eval" (uuid))} (when @session-id {:session @session-id}))) read-seq (atom (cons {:op "clone"} (repeatedly read-msg))) write (fn [{:strs [out err value status ns new-session id] :as msg}] (when new-session (reset! session-id new-session)) (when ns (reset! cns ns)) (doseq [^String x [out err value] :when x] (.write w x)) (when (and (= status #{:done}) id (.startsWith ^String id "eval")) (prompt true)) (.flush w)) read #(let [head (promise)] (swap! read-seq (fn [s] (deliver head (first s)) (rest s))) @head)] (fn-transport read write (when s (swap! read-seq (partial cons {:session @session-id :op "close"})) #(.close s)))))) (defn tty-greeting "A greeting fn usable with clojure.tools.nrepl.server/start-server, meant to be used in conjunction with Transports returned by the `tty` function. Usually, Clojure-aware client-side tooling would provide this upon connecting to the server, but telnet et al. isn't that." [transport] (send transport {:out (str ";; Clojure " (clojure-version) \newline "user=> ")})) (deftype QueueTransport [^BlockingQueue in ^BlockingQueue out] clojure.tools.nrepl.transport.Transport (send [this msg] (.put out msg) this) (recv [this] (.take in)) (recv [this timeout] (.poll in timeout TimeUnit/MILLISECONDS))) (defn piped-transports "Returns a pair of Transports that read from and write to each other." [] (let [a (LinkedBlockingQueue.) b (LinkedBlockingQueue.)] [(QueueTransport. a b) (QueueTransport. b a)])) tools.nrepl-tools.nrepl-0.2.13/src/main/java/000077500000000000000000000000001307046336100207705ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/java/clojure/000077500000000000000000000000001307046336100224335ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/java/clojure/tools/000077500000000000000000000000001307046336100235735ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/java/clojure/tools/nrepl/000077500000000000000000000000001307046336100247135ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/java/clojure/tools/nrepl/Connection.java000066400000000000000000000120271307046336100276570ustar00rootroot00000000000000/** * Copyright (c) Rich Hickey. All rights reserved. * The use and distribution terms for this software are covered by the * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) * which can be found in the file epl-v10.html at the root of this distribution. * By using this software in any fashion, you are agreeing to be bound by * the terms of this license. * You must not remove this notice, or any other, from this software. **/ package clojure.tools.nrepl; import java.io.Closeable; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; import clojure.lang.AFn; import clojure.lang.ArraySeq; import clojure.lang.Delay; import clojure.lang.IFn; import clojure.lang.ISeq; import clojure.lang.Keyword; import clojure.lang.PersistentHashMap; import clojure.lang.RT; import clojure.lang.Seqable; import clojure.lang.Symbol; import clojure.lang.Var; /** * @author Chas Emerick */ public class Connection implements Closeable { static { try { RT.var("clojure.core", "require").invoke(Symbol.intern("clojure.tools.nrepl")); RT.var("clojure.core", "require").invoke(Symbol.intern("clojure.walk")); } catch (Exception e) { throw new RuntimeException(e); } } public static Var find (String ns, String name) { return Var.find(Symbol.intern(ns, name)); } private static Var connect = find("clojure.tools.nrepl", "connect"), urlConnect = find("clojure.tools.nrepl", "url-connect"), createClient = find("clojure.tools.nrepl", "client"), clientSession = find("clojure.tools.nrepl", "client-session"), newSession = find("clojure.tools.nrepl", "new-session"), message = find("clojure.tools.nrepl", "message"), combineResponses = find("clojure.tools.nrepl", "combine-responses"), map = find("clojure.core", "map"), readString = find("clojure.core", "read-string"), stringifyKeys = find("clojure.walk", "stringify-keys"); public final Closeable transport; public final IFn client; public final String url; public Connection (String url) throws Exception { this(url, Long.MAX_VALUE); } public Connection (String url, long readTimeout) throws Exception { transport = (Closeable)urlConnect.invoke(this.url = url); client = (IFn)createClient.invoke(transport, readTimeout); } public void close () throws IOException { transport.close(); } public Response send (String... kvs) { try { Map msg = PersistentHashMap.createWithCheck(kvs); return new Response((ISeq)message.invoke(client, msg)); } catch (Exception e) { throw new RuntimeException(e); } } public Response sendSession (String session, String... kvs) { try { Map msg = PersistentHashMap.createWithCheck(kvs); return new Response((ISeq)message.invoke( clientSession.invoke(client, Keyword.intern("session"), session), msg)); } catch (Exception e) { throw new RuntimeException(e); } } public String newSession (String cloneSessionId) { try { if (cloneSessionId == null) { return (String)newSession.invoke(client); } else { return (String)newSession.invoke(client, Keyword.intern("clone"), cloneSessionId); } } catch (Exception e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") public static class Response implements Seqable { // would prefer to use a Delay here, but the change in IFn.invoke signatures between // Clojure 1.2 and 1.3 makes it impossible to be compatible with both from Java private ISeq responses; private Map response; private Response (final ISeq responses) { this.responses = responses; } public synchronized Map combinedResponse () { try { if (response == null) { response = (Map)stringifyKeys.invoke(combineResponses.invoke(responses)); responses = null; } return response; } catch (Exception e) { throw new RuntimeException(e); } } public Set statuses () { try { return (Set)combinedResponse().get("status"); } catch (Exception e) { throw new RuntimeException(e); } } public List values () { try { return (List)map.invoke(readString, combinedResponse().get("value")); } catch (Exception e) { throw new RuntimeException(e); } } public ISeq seq() { return responses; } } } tools.nrepl-tools.nrepl-0.2.13/src/main/java/clojure/tools/nrepl/StdOutBuffer.java000066400000000000000000000022351307046336100301340ustar00rootroot00000000000000package clojure.tools.nrepl; /** * This class exists solely so that the clojure side can call .setLength under JDK 1.5. * Doing so with a StringBuilder/StringBuffer fails with: * * Can't call public method of non-public class: public void java.lang.AbstractStringBuilder.setLength(int) * * ...as documented in these outstanding bugs: * http://dev.clojure.org/jira/browse/CLJ-126 * http://dev.clojure.org/jira/browse/CLJ-259 */ public class StdOutBuffer { private final StringBuilder sb = new StringBuilder(); public void setLength (int x) { sb.setLength(x); } public int length () { return sb.length(); } public void append(Object x) { sb.append(x); } public void append(char x) { sb.append(x); } public void append(CharSequence s, int start, int end) { sb.append(s, start, end); } public void append(CharSequence s) { sb.append(s); } public void append(char[] s, int start, int len) { sb.append(s, start, len); } public void append(char[] s) { sb.append(s); } public String toString() { return sb.toString(); } } tools.nrepl-tools.nrepl-0.2.13/src/main/java/clojure/tools/nrepl/main.java000066400000000000000000000015651307046336100265110ustar00rootroot00000000000000/** * Copyright (c) Rich Hickey. All rights reserved. * The use and distribution terms for this software are covered by the * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) * which can be found in the file epl-v10.html at the root of this distribution. * By using this software in any fashion, you are agreeing to be bound by * the terms of this license. * You must not remove this notice, or any other, from this software. **/ package clojure.tools.nrepl; import clojure.lang.RT; import clojure.lang.Symbol; import clojure.lang.Var; /** * @author Chas Emerick */ public class main { public static void main (String[] args) throws Exception { RT.var("clojure.core", "require").invoke(Symbol.intern("clojure.tools.nrepl.cmdline")); RT.var("clojure.tools.nrepl.cmdline", "-main").applyTo(RT.seq(args)); } } tools.nrepl-tools.nrepl-0.2.13/src/main/resources/000077500000000000000000000000001307046336100220615ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/resources/clojure/000077500000000000000000000000001307046336100235245ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/resources/clojure/tools/000077500000000000000000000000001307046336100246645ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/resources/clojure/tools/nrepl/000077500000000000000000000000001307046336100260045ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/main/resources/clojure/tools/nrepl/version.txt000066400000000000000000000000161307046336100302270ustar00rootroot00000000000000${pom.version}tools.nrepl-tools.nrepl-0.2.13/src/test/000077500000000000000000000000001307046336100201025ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/000077500000000000000000000000001307046336100215455ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/000077500000000000000000000000001307046336100232105ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/000077500000000000000000000000001307046336100243505ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/000077500000000000000000000000001307046336100254705ustar00rootroot00000000000000tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/bencode_test.clj000066400000000000000000000145131307046336100306240ustar00rootroot00000000000000;- ; Copyright (c) Meikel Brandmeyer. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns clojure.tools.nrepl.bencode-test (:import java.io.ByteArrayInputStream java.io.ByteArrayOutputStream java.io.PushbackInputStream clojure.lang.RT) (:require [clojure.java.io :as io]) (:use [clojure.test :only [deftest is are]] [clojure.tools.nrepl.bencode :as bencode])) (defn #^{:private true} >bytes [#^String input] (.getBytes input "UTF-8")) (defmulti #^{:private true} > input (map (fn [[k v]] [k ( bytes ByteArrayInputStream. PushbackInputStream. reader)) (defn- >input [^String input & args] (-> input (.getBytes "UTF-8") (#(apply decode % args)) input x :reader read-netstring) y) "0:," "" "13:Hello, World!," "Hello, World!" "16:Hällö, Würld!," "Hällö, Würld!" "25:Здравей, Свят!," "Здравей, Свят!")) (deftest test-string-reading (are [x y] (= (>input x :reader read-bencode) y) "0:" "" "13:Hello, World!" "Hello, World!" "16:Hällö, Würld!" "Hällö, Würld!" "25:Здравей, Свят!" "Здравей, Свят!")) (deftest test-integer-reading (are [x y] (= (>input x :reader read-bencode) y) "i0e" 0 "i42e" 42 "i-42e" -42)) (deftest test-list-reading (are [x y] (= (>input x :reader read-bencode) y) "le" [] "l6:cheesee" ["cheese"] "l6:cheese3:ham4:eggse" ["cheese" "ham" "eggs"])) (deftest test-map-reading (are [x y] (= (>input x :reader read-bencode) y) "de" {} "d3:ham4:eggse" {"ham" "eggs"})) (deftest test-nested-reading (are [x y] (= (>input x :reader read-bencode) y) "l6:cheesei42ed3:ham4:eggsee" ["cheese" 42 {"ham" "eggs"}] "d6:cheesei42e3:haml4:eggsee" {"cheese" 42 "ham" ["eggs"]})) (defn- >stream [thing & {:keys [writer]}] (doto (ByteArrayOutputStream.) (writer thing))) (defn- >output [& args] (.toString (apply >stream args) "UTF-8")) (deftest test-netstring-writing (are [x y] (= (>output (>bytes x) :writer write-netstring) y) "" "0:," "Hello, World!" "13:Hello, World!," "Hällö, Würld!" "16:Hällö, Würld!," "Здравей, Свят!" "25:Здравей, Свят!,")) (deftest test-byte-array-writing (are [x y] (= (>output (>bytes x) :writer write-bencode) y) "" "0:" "Hello, World!" "13:Hello, World!" "Hällö, Würld!" "16:Hällö, Würld!" "Здравей, Свят!" "25:Здравей, Свят!")) (deftest test-string-writing (are [x y] (= (>output x :writer write-bencode) y) "" "0:" "Hello, World!" "13:Hello, World!" "Hällö, Würld!" "16:Hällö, Würld!" "Здравей, Свят!" "25:Здравей, Свят!")) (deftest test-input-stream-writing (are [x y] (= (>output (ByteArrayInputStream. (>bytes x)) :writer write-bencode) y) "" "0:" "Hello, World!" "13:Hello, World!" "Hällö, Würld!" "16:Hällö, Würld!" "Здравей, Свят!" "25:Здравей, Свят!")) (deftest test-integer-writing (are [x y] (= (>output x :writer write-bencode) y) 0 "i0e" 42 "i42e" -42 "i-42e" ; Works for all integral types. ; Note: BigInts (42N) not tested, since they are not ; supported in 1.2. (Byte. "42") "i42e" (Short. "42") "i42e" (Integer. "42") "i42e" (Long. "42") "i42e")) (deftest test-named-writing (are [x y] (= (>output x :writer write-bencode) y) :foo "3:foo" :foo/bar "7:foo/bar" 'foo "3:foo" 'foo/bar "7:foo/bar")) (deftest test-list-writing (are [x y] (= (>output x :writer write-bencode) y) nil "le" [] "le" ["cheese"] "l6:cheesee" ["cheese" "ham" "eggs"] "l6:cheese3:ham4:eggse")) (deftest test-map-writing (are [x y] (= (>output x :writer write-bencode) y) {} "de" {"ham" "eggs"} "d3:ham4:eggse")) (deftest test-nested-writing (are [x y] (= (>output x :writer write-bencode) y) ["cheese" 42 {"ham" "eggs"}] "l6:cheesei42ed3:ham4:eggsee" {"cheese" 42 "ham" ["eggs"]} "d6:cheesei42e3:haml4:eggsee")) (deftest test-lexicographic-sorting (let [source ["ham" "eggs" "hamburg" "hamburger" "cheese"] expected ["cheese" "eggs" "ham" "hamburg" "hamburger"] to-test (->> source (map >bytes) (sort @#'clojure.tools.nrepl.bencode/lexicographically) (map > [-119 80 78 71 13 10 26 10 0 0 0 13 73 72 68 82 0 0 0 100 0 0 0 100 8 6 0 0 0 112 -30 -107 84 0 0 3 -16 105 67 67 80 73 67 67 32 80 114 111 102 105 108 101 0 0 40 -111 -115 85 -35 111 -37 84 20 63 -119 111 92 -92 22 63 -96 -79 -114 14 21 -117 -81 85 83 91 -71 27 26 -83 -58 6 73 -109 -91 -23 66 26 -71 -51 -40 42 -92 -55 117 110] (map byte) (into-array Byte/TYPE))] (is (= (seq binary-data) (-> {"data" binary-data} (>stream :writer write-bencode) .toByteArray (decode :reader read-bencode) (get "data") seq))))) tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/cmdline_test.clj000066400000000000000000000040671307046336100306430ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns #^{:author "Chas Emerick"} clojure.tools.nrepl.cmdline-test (:use [clojure.tools.nrepl-test :only (def-repl-test repl-server-fixture *server*)] clojure.test) (:require [clojure.tools.nrepl :as repl])) (use-fixtures :once repl-server-fixture) (comment ;TODO (def-repl-test ack (repl/reset-ack-port!) (let [server-process (.exec (Runtime/getRuntime) (into-array ["java" "-Dnreplacktest=y" "-cp" (System/getProperty "java.class.path") "clojure.tools.nrepl.main" "--ack" (str (:port *server*))])) acked-port (repl/wait-for-ack 20000)] (try (is acked-port "Timed out waiting for ack") (when acked-port (with-open [c2 (repl/connect acked-port)] ; just a sanity check (is (= "y" (-> (((:send c2) "(System/getProperty \"nreplacktest\")")) repl/read-response-value :value))))) (finally (.destroy server-process))))) (def-repl-test explicit-port-argument (repl/reset-ack-port!) (let [free-port (with-open [ss (java.net.ServerSocket.)] (.bind ss nil) (.getLocalPort ss)) server-process (.exec (Runtime/getRuntime) (into-array ["java" "-Dnreplacktest=y" "-cp" (System/getProperty "java.class.path") "clojure.tools.nrepl.main" "--port" (str free-port) "--ack" (str (:port *server*))])) acked-port (repl/wait-for-ack 20000)] (try (is acked-port "Timed out waiting for ack") (is (= acked-port free-port)) (finally (.destroy server-process))))))tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/describe_test.clj000066400000000000000000000042231307046336100310020ustar00rootroot00000000000000(ns ^{:author "Chas Emerick"} clojure.tools.nrepl.describe-test (:use [clojure.tools.nrepl-test :only (def-repl-test repl-server-fixture project-base-dir)] clojure.test) (:require [clojure.tools.nrepl :as nrepl] [clojure.tools.nrepl.middleware :as middleware] [clojure.java.io :as io])) (use-fixtures :once repl-server-fixture) (def ^{:private true} op-names #{:load-file :ls-sessions :interrupt :stdin :describe :eval :close :clone}) (def-repl-test simple-describe (let [{{:keys [nrepl clojure java]} :versions ops :ops} (nrepl/combine-responses (nrepl/message timeout-client {:op "describe"}))] (testing "versions" (when-not (every? #(contains? java %) [:major :minor :incremental :update]) (println "Got less information out of `java.version` than we'd like:" (System/getProperty "java.version") "=>" java)) (is (= (#'middleware/safe-version clojure.tools.nrepl/version) nrepl)) (is (= (#'middleware/safe-version *clojure-version*) (dissoc clojure :version-string))) (is (= (clojure-version) (:version-string clojure))) (is (= (System/getProperty "java.version") (:version-string java)))) (is (= op-names (set (keys ops)))) (is (every? empty? (map val ops))))) (def-repl-test verbose-describe (let [{:keys [ops aux]} (nrepl/combine-responses (nrepl/message timeout-client {:op "describe" :verbose? "true"}))] (is (= op-names (set (keys ops)))) (is (every? seq (map (comp :doc val) ops))) (is (= {:current-ns "user"} aux)))) ; quite misplaced, but this'll do for now... (def-repl-test update-op-docs (let [describe-response (nrepl/combine-responses (nrepl/message timeout-client {:op "describe" :verbose? "true"}))] (spit (io/file project-base-dir "doc" "ops.md") (str "\n" (#'middleware/describe-markdown describe-response))))) tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/helpers_test.clj000066400000000000000000000040761307046336100306720ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns ^{:author "Chas Emerick"} clojure.tools.nrepl.helpers-test (:import (java.io File)) (:use [clojure.tools.nrepl-test :only (def-repl-test repl-server-fixture)] clojure.test) (:require [clojure.tools.nrepl :as nrepl] [clojure.tools.nrepl.helpers :as helpers])) (def project-base-dir (File. (System/getProperty "nrepl.basedir" "."))) (use-fixtures :once repl-server-fixture) (def-repl-test load-code-with-debug-info ;; bizarrely, the path of the test script generated by clojure-maven-plugin ;; ends up being in the :file metadata here on Clojure 1.3.0+, but ;; passes in 1.2.0... #_(repl-eval session "\n\n\n(defn function [])") #_(is (= [{:file "NO_SOURCE_PATH" :line 4}] (repl-values session "(-> #'function meta (select-keys [:file :line]))"))) (repl-values session (helpers/load-file-command "\n\n\n\n\n\n\n\n\n(defn dfunction [])" "path/from/source/root.clj" "root.clj")) (is (= [{:file "path/from/source/root.clj" :line 10}] (repl-values session (nrepl/code (-> #'dfunction meta (select-keys [:file :line]))))))) (def-repl-test load-file-with-debug-info (repl-values session (helpers/load-file-command (File. project-base-dir "load-file-test/clojure/tools/nrepl/load_file_sample.clj") (File. project-base-dir "load-file-test"))) (is (= [{:file "clojure/tools/nrepl/load_file_sample.clj" :line 5}] (repl-values session (nrepl/code (-> #'clojure.tools.nrepl.load-file-sample/dfunction meta (select-keys [:file :line])))))))tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/load_file_test.clj000066400000000000000000000054241307046336100311440ustar00rootroot00000000000000(ns ^{:author "Chas Emerick"} clojure.tools.nrepl.load-file-test (:import (java.io File)) (:use [clojure.tools.nrepl-test :only (def-repl-test repl-server-fixture project-base-dir)] clojure.test) (:require [clojure.tools.nrepl :as nrepl])) (use-fixtures :each repl-server-fixture) (def-repl-test load-code-with-debug-info (doall (nrepl/message timeout-session {:op "load-file" :file "\n\n\n(defn function [])"})) (is (contains? ; different versions of Clojure use different default :file metadata #{[{:file "NO_SOURCE_PATH" :line 4}] [{:file "NO_SOURCE_FILE" :line 4}]} (repl-values timeout-session (nrepl/code (-> #'function meta (select-keys [:file :line])))))) (doall (nrepl/message timeout-session {:op "load-file" :file "\n\n\n\n\n\n\n\n\n(defn afunction [])" :file-path "path/from/source/root.clj" :file-name "root.clj"})) (is (= [{:file "path/from/source/root.clj" :line 10}] (repl-values timeout-session (nrepl/code (-> #'afunction meta (select-keys [:file :line]))))))) (def-repl-test load-file-with-debug-info (doall (nrepl/message timeout-session {:op "load-file" :file (slurp (File. project-base-dir "load-file-test/clojure/tools/nrepl/load_file_sample.clj")) :file-path "clojure/tools/nrepl/load_file_sample.clj" :file-name "load_file_sample.clj"})) (is (= [{:file "clojure/tools/nrepl/load_file_sample.clj" :line 5}] (repl-values timeout-session (nrepl/code (-> #'clojure.tools.nrepl.load-file-sample/dfunction meta (select-keys [:file :line]))))))) (def-repl-test load-file-with-print-vars (set! *print-length* 3) (set! *print-level* 3) (doall (nrepl/message session {:op "load-file" :file "(def a (+ 1 (+ 2 (+ 3 (+ 4 (+ 5 6)))))) (def b 2) (def c 3) (def ^{:internal true} d 4)" :file-path "path/from/source/root.clj" :file-name "root.clj"})) (is (= [4] (repl-values session (nrepl/code d))))) (def-repl-test load-file-response-no-ns (is (not (contains? (nrepl/combine-responses (nrepl/message session {:op "load-file" :file "(ns foo) (def x 5)" :file-path "/path/to/source.clj" :file-name "source.clj"})) :ns)))) tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/middleware_test.clj000066400000000000000000000066271307046336100313510ustar00rootroot00000000000000(ns clojure.tools.nrepl.middleware-test (:require (clojure.tools.nrepl.middleware interruptible-eval load-file pr-values session)) (:use [clojure.tools.nrepl.middleware :as middleware] clojure.test)) ; wanted to just use resolve to avoid the long var names, but ; it seems that unqualified resolves *don't work* within the context of a ; clojure-maven-plugin test execution?!? (def ^{:private true} default-middlewares [#'clojure.tools.nrepl.middleware.session/add-stdin #'clojure.tools.nrepl.middleware.load-file/wrap-load-file #'clojure.tools.nrepl.middleware/wrap-describe #'clojure.tools.nrepl.middleware.session/session #'clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval]) (defn- wonky-resolve [s] (if (symbol? s) (resolve s) s)) (defn- indexed-stack [x] (->> x (map wonky-resolve) shuffle linearize-middleware-stack (map-indexed #(vector (if (var? %2) (-> (#'middleware/var-name %2) symbol name symbol) %2) %)) (into {}))) (deftest sanity (let [stack (indexed-stack default-middlewares)] (is (stack 'pr-values)) (are [before after] (< (stack before) (stack after)) 'interruptible-eval 'wrap-load-file 'interruptible-eval 'session 'wrap-describe 'pr-values 'interruptible-eval 'pr-values)) (let [n ^{::middleware/descriptor {:expects #{"clone"} :requires #{}}} {:dummy :middleware2} m ^{::middleware/descriptor {:expects #{"eval"} :requires #{n #'clojure.tools.nrepl.middleware.pr-values/pr-values}}} {:dummy :middleware} q ^{::middleware/descriptor {:expects #{} :requires #{"describe" "eval"}}} {:dummy :middleware3} stack (indexed-stack (concat default-middlewares [m q n]))] ;(->> stack clojure.set/map-invert (into (sorted-map)) vals println) (are [before after] (< (stack before) (stack after)) 'interruptible-eval m m 'pr-values 'session n q 'wrap-describe m n 'interruptible-eval 'wrap-load-file 'interruptible-eval 'session 'wrap-describe 'pr-values 'interruptible-eval 'pr-values))) (deftest append-dependency-free-middleware (let [m ^{::middleware/descriptor {:expects #{} :requires #{}}} {:dummy :middleware} n {:dummy "This not-middleware is supposed to be sans-descriptor, don't panic!"} stack (->> (concat default-middlewares [m n]) shuffle linearize-middleware-stack)] (is (= #{n m} (set (take-last 2 stack)))))) (deftest no-descriptor-warning (is (.contains (with-out-str (binding [*err* *out*] (indexed-stack (conj default-middlewares {:dummy :middleware})))) "No nREPL middleware descriptor in metadata of {:dummy :middleware}"))) (deftest NREPL-53-regression (is (= [0 1 2] (map :id (linearize-middleware-stack [^{::middleware/descriptor {:expects #{} :requires #{"1"}}} {:id 0} ^{::middleware/descriptor {:expects #{} :requires #{} :handles {"1" {}}}} {:id 1} ^{::middleware/descriptor {:expects #{"1"} :requires #{}}} {:id 2}]))))) tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/pprinting_test.clj000066400000000000000000000027531307046336100312420ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns #^{:author "Chas Emerick"} clojure.tools.nrepl.pprinting-test (:use [clojure.tools.nrepl-test :only (def-repl-test repl-server-fixture)] clojure.test) (:require [clojure.tools.nrepl :as repl])) (use-fixtures :once repl-server-fixture) (comment ; TODO (defmacro def-pp-test [name & body] (when (repl/pretty-print-available?) `(def-repl-test ~name (~'repl-receive "(set! clojure.tools.nrepl/*pretty-print* true)") ~@body))) (def-pp-test simple-collection (is (< 20 (->> (repl "(range 100)") repl/response-seq repl/combine-responses :value first (filter #(= \newline %)) count)))) (def-pp-test toggle-pprinting (is (repl-value "clojure.tools.nrepl/*pretty-print*")) (is (repl-value "(clojure.tools.nrepl/pretty-print?)")) (repl-receive "(set! clojure.tools.nrepl/*pretty-print* false)") (is (not (repl-value "clojure.tools.nrepl/*pretty-print*"))) (is (not (repl-value "(clojure.tools.nrepl/pretty-print?)")))))tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/response_test.clj000066400000000000000000000031371307046336100310630ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns clojure.tools.nrepl.response-test (:use clojure.test [clojure.tools.nrepl.transport :only (piped-transports) :as t]) (:require [clojure.tools.nrepl :as repl]) (:import (java.util.concurrent BlockingQueue LinkedBlockingQueue TimeUnit))) (deftest response-seq (let [[local remote] (piped-transports)] (doseq [x (range 10)] (t/send remote x)) (is (= (range 10) (repl/response-seq local 0))) ; ensure timeouts don't capture later responses (repl/response-seq local 100) (doseq [x (range 10)] (t/send remote x)) (is (= (range 10) (repl/response-seq local 0))))) (deftest client (let [[local remote] (piped-transports) client (repl/client local 100)] (doseq [x (range 10)] (t/send remote x)) (is (= (range 10) (client 17))) (is (= 17 (t/recv remote))))) (deftest client-heads (let [[local remote] (piped-transports) client (repl/client local Long/MAX_VALUE) all-seq (client)] (doseq [x (range 10)] (t/send remote x)) (is (= [0 1 2] (take 3 all-seq))) (is (= (range 3 7) (take 4 (client :a)))) (is (= :a (t/recv remote))) (is (= (range 10) (take 10 all-seq))))) tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl/sanity_test.clj000066400000000000000000000113101307046336100305240ustar00rootroot00000000000000; Copyright (c) Rich Hickey. All rights reserved. ; The use and distribution terms for this software are covered by the ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ; which can be found in the file epl-v10.html at the root of this distribution. ; By using this software in any fashion, you are agreeing to be bound by ; the terms of this license. ; You must not remove this notice, or any other, from this software. (ns clojure.tools.nrepl.sanity-test (:use clojure.test [clojure.tools.nrepl.transport :only (piped-transports)]) (: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))) (println (format "Testing with Clojure v%s on %s" (clojure-version) (System/getProperty "java.version"))) (defn- internal-eval ([expr] (internal-eval nil expr)) ([ns expr] (let [[local remote] (piped-transports) out (java.io.StringWriter.) err (java.io.StringWriter.) expr (if (string? expr) expr (binding [*print-meta* true] (pr-str expr))) msg (merge {:code expr :transport remote} (when ns {:ns ns})) resp-fn (if ns (juxt :ns :value) :value)] (eval/evaluate {#'*out* (java.io.PrintWriter. out) #'*err* (java.io.PrintWriter. err)} msg) (->> (repl/response-seq local 0) (map resp-fn) (cons (str out)) (#(if (seq (str err)) (cons (str err) %) %)))))) (deftest eval-sanity (try (are [result expr] (= result (internal-eval expr)) ["" 3] '(+ 1 2) ["" nil] '*1 ["" nil] '(do (def ^{:dynamic true} ++ +) nil) ["" 5] '(binding [++ -] (++ 8 3)) ["" 42] '(set! *print-length* 42) ["" nil] '*print-length*) (finally (ns-unmap *ns* '++)))) (deftest specified-namespace (try (are [ns result expr] (= result (internal-eval ns expr)) (ns-name *ns*) ["" [(str (ns-name *ns*)) 3]] '(+ 1 2) 'user ["" ["user" '("user" "++")]] '(do (def ^{:dynamic true} ++ +) (map #(-> #'++ meta % str) [:ns :name])) (ns-name *ns*) ["" [(str (ns-name *ns*)) 5]] '(binding [user/++ -] (user/++ 8 3))) (finally (ns-unmap 'user '++)))) (deftest multiple-expressions (are [result expr] (= result (internal-eval expr)) ["" 4 65536.0] "(+ 1 3) (Math/pow 2 16)" ["" 4 20 1 0] "(+ 2 2) (* *1 5) (/ *2 4) (- *3 4)" ["" nil] '*1)) (deftest stdout-stderr (are [result expr] (= result (internal-eval expr)) ["5 6 7 \n 8 9 10\n" nil] '(println 5 6 7 \newline 8 9 10) ["user/foo\n" "" nil] '(binding [*out* *err*] (prn 'user/foo)) ["problem" "" :value] '(do (.write *err* "problem") :value)) (is (re-seq #"Exception: No such var: user/foo" (-> '(prn user/foo) internal-eval first)))) (deftest repl-out-writer (let [[local remote] (piped-transports) w (#'session/session-out :out :dummy-session-id remote)] (doto w .flush (.println "println") (.write "abcd") (.write (.toCharArray "ef") 0 2) (.write "gh" 0 2) (.write (.toCharArray "ij")) (.write " klm" 5 1) (.write 32) .flush) (with-open [out (java.io.PrintWriter. w)] (binding [*out* out] (newline) (prn #{}) (flush))) (is (= ["println\n" "abcdefghijm " "\n#{}\n"] (->> (repl/response-seq local 0) (map :out)))))) ; TODO (comment (def-repl-test auto-print-stack-trace (is (= true (repl-value "(set! clojure.tools.nrepl/*print-detail-on-error* true)"))) (is (.contains (-> (repl "(throw (Exception. \"foo\" (Exception. \"nested exception\")))") full-response :err) "nested exception"))) (def-repl-test install-custom-error-detail-fn (->> (repl/send-with connection (set! clojure.tools.nrepl/*print-error-detail* (fn [ex] (print "custom printing!"))) (set! clojure.tools.nrepl/*print-detail-on-error* true)) repl/response-seq doall) (is (= "custom printing!" (->> (repl/send-with connection (throw (Exception. "foo"))) full-response :err)))) )tools.nrepl-tools.nrepl-0.2.13/src/test/clojure/clojure/tools/nrepl_test.clj000066400000000000000000000445011307046336100272250ustar00rootroot00000000000000(ns clojure.tools.nrepl-test (:import java.net.SocketException java.io.File) (:use clojure.test [clojure.tools.nrepl :as nrepl]) (:require (clojure.tools.nrepl [transport :as transport] [server :as server] [ack :as ack]) [clojure.set :as set])) (def project-base-dir (File. (System/getProperty "nrepl.basedir" "."))) (def ^{:dynamic true} *server* nil) (defn repl-server-fixture [f] (with-open [server (server/start-server)] (binding [*server* server] (f) (set! *print-length* nil) (set! *print-level* nil)))) (use-fixtures :each repl-server-fixture) (defmacro def-repl-test [name & body] `(deftest ~(with-meta name {:private true}) (with-open [transport# (connect :port (:port *server*))] (let [~'transport transport# ~'client (client transport# Long/MAX_VALUE) ~'session (client-session ~'client) ~'timeout-client (client transport# 1000) ~'timeout-session (client-session ~'timeout-client) ~'repl-eval #(message % {:op :eval :code %2}) ~'repl-values (comp response-values ~'repl-eval)] ~@body)))) (def-repl-test eval-literals (are [literal] (= (binding [*ns* (find-ns 'user)] ; needed for the ::keyword (-> literal read-string eval list)) (repl-values client literal)) "5" "0xff" "5.1" "-2e12" "1/4" "'symbol" "'namespace/symbol" ":keyword" "::local-ns-keyword" ":other.ns/keyword" "\"string\"" "\"string\\nwith\\r\\nlinebreaks\"" "'(1 2 3)" "[1 2 3]" "{1 2 3 4}" "#{1 2 3 4}") (is (= (->> "#\"regex\"" read-string eval list (map str)) (->> "#\"regex\"" (repl-values client) (map str))))) (def-repl-test simple-expressions (are [expr] (= [(eval expr)] (repl-values client (pr-str expr))) '(range 40) '(apply + (range 100)))) (def-repl-test defining-fns (repl-values client "(defn x [] 6)") (is (= [6] (repl-values client "(x)")))) (defn- dumb-alternative-eval [form] (let [result (eval form)] (if (number? result) (- result) result))) (def-repl-test use-alternative-eval-fn (is (= {:value ["-124750"]} (-> (message timeout-client {:op :eval :eval "clojure.tools.nrepl-test/dumb-alternative-eval" :code "(reduce + (range 500))"}) combine-responses (select-keys [:value]))))) (def-repl-test source-tracking-eval (let [sym (name (gensym)) request {:op :eval :ns "user" :code (format "(def %s 1000)" sym) :file "test.clj" :line 42 :column 10} _ (doall (message timeout-client request)) meta (meta (resolve (symbol "user" sym)))] (is (= (:file meta) "test.clj")) (is (= (:line meta) 42)) (is (= (:column meta) (if (< (:minor *clojure-version*) 5) nil 10))))) (def-repl-test unknown-op (is (= {:op "abc" :status #{"error" "unknown-op" "done"}} (-> (message timeout-client {:op :abc}) combine-responses (select-keys [:op :status]))))) (def-repl-test session-lifecycle (is (= #{"error" "unknown-session"} (-> (message timeout-client {:session "abc"}) combine-responses :status))) (let [session-id (new-session timeout-client) session-alive? #(contains? (-> (message timeout-client {:op :ls-sessions}) combine-responses :sessions set) session-id)] (is session-id) (is (session-alive?)) (is (= #{"done" "session-closed"} (-> (message timeout-client {:op :close :session session-id}) combine-responses :status))) (is (not (session-alive?))))) (def-repl-test separate-value-from-*out* (is (= {:value [nil] :out "5\n"} (-> (map read-response-value (repl-eval client "(println 5)")) combine-responses (select-keys [:value :out]))))) (def-repl-test sessionless-*out* (is (= "5\n:foo\n" (-> (repl-eval client "(println 5)(println :foo)") combine-responses :out)))) (def-repl-test session-*out* (is (= "5\n:foo\n" (-> (repl-eval session "(println 5)(println :foo)") combine-responses :out)))) (def-repl-test error-on-lazy-seq-with-side-effects (let [expression '(let [foo (fn [] (map (fn [x] (println x) (throw (Exception. "oops"))) [1 2 3]))] (foo)) results (-> (repl-eval session (pr-str expression)) combine-responses)] (is (= "1\n" (:out results))) (is (re-seq #"oops" (:err results))))) (def-repl-test cross-transport-*out* (let [sid (-> session meta ::nrepl/taking-until :session) transport2 (nrepl/connect :port (:port *server*))] (transport/send transport2 {"op" "eval" "code" "(println :foo)" "session" sid}) (is (->> (repeatedly #(transport/recv transport2 1000)) (take-while identity) (some #(= ":foo\n" (:out %))))))) (def-repl-test streaming-out (is (= (for [x (range 10)] (str x \newline)) (->> (repl-eval client "(dotimes [x 10] (println x))") (map :out) (remove nil?))))) (def-repl-test session-*out*-writer-length-translation (when (<= 4 (:minor *clojure-version*)) (is (= "#inst \"2013-02-11T12:13:44.000+00:00\"\n" (-> (repl-eval session (code (println (doto (java.util.GregorianCalendar. 2013 1 11 12 13 44) (.setTimeZone (java.util.TimeZone/getTimeZone "GMT")))))) combine-responses :out))))) (def-repl-test streaming-out-without-explicit-flushing (is (= ["(0 1 " "2 3 4" " 5 6 " "7 8 9" " 10)"] ; new session (->> (message client {:op :eval :out-limit 5 :code "(print (range 11))"}) (map :out) (remove nil?)) ; existing session (->> (message session {:op :eval :out-limit 5 :code "(print (range 11))"}) (map :out) (remove nil?))))) (def-repl-test ensure-whitespace-prints (is (= " \t \n \f \n" (->> (repl-eval client "(println \" \t \n \f \")") combine-responses :out)))) (def-repl-test session-return-recall (testing "sessions persist across connections" (repl-values session (code (apply + (range 6)) (str 12 \c) (keyword "hello"))) (with-open [separate-connection (connect :port (:port *server*))] (let [history [[15 "12c" :hello]] sid (-> session meta :clojure.tools.nrepl/taking-until :session) sc-session (-> separate-connection (nrepl/client 1000) (nrepl/client-session :session sid))] (is (= history (repl-values sc-session "[*3 *2 *1]"))) (is (= history (repl-values sc-session "*1")))))) (testing "without a session id, REPL-bound vars like *1 have default values" (is (= [nil] (repl-values client "*1"))))) (def-repl-test session-set! (repl-eval session (code (set! *compile-path* "badpath") (set! *warn-on-reflection* true))) (is (= [["badpath" true]] (repl-values session (code [*compile-path* *warn-on-reflection*]))))) (def-repl-test exceptions (let [{:keys [status err value]} (combine-responses (repl-eval session "(throw (Exception. \"bad, bad code\"))"))] (is (= #{"eval-error" "done"} status)) (is (nil? value)) (is (.contains err "bad, bad code")) (is (= [true] (repl-values session "(.contains (str *e) \"bad, bad code\")"))))) (def-repl-test multiple-expressions-return (is (= [5 18] (repl-values session "5 (/ 5 0) (+ 5 6 7)")))) (def-repl-test return-on-incomplete-expr (let [{:keys [out status value]} (combine-responses (repl-eval session "(missing paren"))] (is (nil? value)) (is (= #{"done" "eval-error"} status)) (is (re-seq #"EOF while reading" (first (repl-values session "(.getMessage *e)")))))) (def-repl-test switch-ns (is (= "otherns" (-> (repl-eval session "(ns otherns) (defn function [] 12)") combine-responses :ns))) (is (= [12] (repl-values session "(function)"))) (repl-eval session "(in-ns 'user)") (is (= [12] (repl-values session "(otherns/function)")))) (def-repl-test switch-ns-2 (is (= "otherns" (-> (repl-eval session (code (ns otherns) (defn function [] 12))) combine-responses :ns))) (is (= [12] (repl-values session "(function)"))) (repl-eval session "(in-ns 'user)") (is (= [12] (repl-values session "(otherns/function)"))) (is (= "user" (-> (repl-eval session "nil") combine-responses :ns)))) (def-repl-test explicit-ns (is (= "user" (-> (repl-eval session "nil") combine-responses :ns))) (is (= "baz" (-> (repl-eval session (code (def bar 5) (ns baz))) combine-responses :ns))) (is (= [5] (response-values (message session {:op :eval :code "bar" :ns "user"})))) ; NREPL-72: :ns argument to eval shouldn't affect *ns* outside of the scope of that evaluation (is (= "baz" (-> (repl-eval session "5") combine-responses :ns)))) (def-repl-test error-on-nonexistent-ns (is (= #{"error" "namespace-not-found" "done"} (-> (message timeout-client {:op :eval :code "(+ 1 1)" :ns (name (gensym))}) combine-responses :status)))) (def-repl-test proper-response-ordering (is (= [[nil "100\n"] ; printed number ["nil" nil] ; return val from println ["42" nil] ; return val from `42` [nil nil]] ; :done (map (juxt :value :out) (repl-eval client "(println 100) 42"))))) (def-repl-test interrupt (is (= #{"error" "interrupt-id-mismatch" "done"} (-> (message client {:op :interrupt :interrupt-id "foo"}) first :status set))) (let [resp (message session {:op :eval :code (code (do (def halted? true) halted? (Thread/sleep 30000) (def halted? false)))})] (Thread/sleep 100) (is (= #{"done"} (-> session (message {:op :interrupt}) first :status set))) (is (= #{"done" "interrupted"} (-> resp combine-responses :status))) (is (= [true] (repl-values session "halted?"))))) ; NREPL-66: ensure that bindings of implementation vars aren't captured by user sessions ; (https://github.com/clojure-emacs/cider/issues/785) (def-repl-test ensure-no-*msg*-capture (let [[r1 r2 :as results] (repeatedly 2 #(repl-eval session "(println :foo)")) [ids ids2] (map #(set (map :id %)) results) [out1 out2] (map #(-> % combine-responses :out) results)] (is (empty? (clojure.set/intersection ids ids2))) (is (= ":foo\n" out1 out2)))) (def-repl-test read-timeout (is (nil? (repl-values timeout-session "(Thread/sleep 1100) :ok"))) ; just getting the values off of the wire so the server side doesn't ; toss a spurious stack trace when the client disconnects (is (= [nil :ok] (->> (repeatedly #(transport/recv transport 500)) (take-while (complement nil?)) response-values)))) (def-repl-test concurrent-message-handling (testing "multiple messages can be handled on the same connection concurrently" (let [sessions (doall (repeatedly 3 #(client-session client))) start-time (System/currentTimeMillis) elapsed-times (map (fn [session eval-duration] (let [expr (pr-str `(Thread/sleep ~eval-duration)) responses (message session {:op :eval :code expr})] (future (is (= [nil] (response-values responses))) (- (System/currentTimeMillis) start-time)))) sessions [2000 1000 0])] (is (apply > (map deref (doall elapsed-times))))))) (def-repl-test ensure-transport-closeable (is (= [5] (repl-values session "5"))) (is (instance? java.io.Closeable transport)) (.close transport) (is (thrown? java.net.SocketException (repl-values session "5")))) ; test is flaking on hudson, but passing locally! :-X #_(def-repl-test ensure-server-closeable (.close *server*) (is (thrown? java.net.ConnectException (connect :port (:port *server*))))) ; wasn't added until Clojure 1.3.0 (defn- root-cause "Returns the initial cause of an exception or error by peeling off all of its wrappers" [^Throwable t] (loop [cause t] (if-let [cause (.getCause cause)] (recur cause) cause))) (defn- disconnection-exception? [e] ; thrown? should check for the root cause! (and (instance? SocketException (root-cause e)) (re-matches #".*(lost.*connection|socket closed).*" (.getMessage (root-cause e))))) (deftest transports-fail-on-disconnects (testing "Ensure that transports fail ASAP when the server they're connected to goes down." (let [server (server/start-server) transport (connect :port (:port server))] (transport/send transport {"op" "eval" "code" "(+ 1 1)"}) (let [reader (future (while true (transport/recv transport)))] (Thread/sleep 1000) (.close server) (Thread/sleep 1000) ; no deref with timeout in Clojure 1.2.0 :-( (try (.get reader 10000 java.util.concurrent.TimeUnit/MILLISECONDS) (is false "A reader started prior to the server closing should throw an error...") (catch Throwable e (is (disconnection-exception? e))))) (is (thrown? SocketException (transport/recv transport))) ;; TODO no idea yet why two sends are *sometimes* required to get a failure (try (transport/send transport {"op" "eval" "code" "(+ 5 1)"}) (catch Throwable t)) (is (thrown? SocketException (transport/send transport {"op" "eval" "code" "(+ 5 1)"})))))) (def-repl-test clients-fail-on-disconnects (testing "Ensure that clients fail ASAP when the server they're connected to goes down." (let [resp (repl-eval client "1 2 3 4 5 6 7 8 9 10")] (is (= "1" (-> resp first :value))) (Thread/sleep 1000) (.close *server*) (Thread/sleep 1000) (try ; these responses were on the wire before the remote transport was closed (is (> 20 (count resp))) (transport/recv transport) (is false "reads after the server is closed should fail") (catch Throwable t (is (disconnection-exception? t))))) ;; TODO as noted in transports-fail-on-disconnects, *sometimes* two sends are needed ;; to trigger an exception on send to an unavailable server (try (repl-eval session "(+ 1 1)") (catch Throwable t)) (is (thrown? SocketException (repl-eval session "(+ 1 1)"))))) (def-repl-test request-*in* (is (= '((1 2 3)) (response-values (for [resp (repl-eval session "(read)")] (do (when (-> resp :status set (contains? "need-input")) (session {:op :stdin :stdin "(1 2 3)"})) resp))))) (session {:op :stdin :stdin "a\nb\nc\n"}) (doseq [x "abc"] (is (= [(str x)] (repl-values session "(read-line)"))))) (def-repl-test request-*in*-eof (is (= nil (response-values (for [resp (repl-eval session "(read)")] (do (when (-> resp :status set (contains? "need-input")) (session {:op :stdin :stdin []})) resp)))))) (def-repl-test request-multiple-read-newline-*in* (is (= '(:ohai) (response-values (for [resp (repl-eval session "(read)")] (do (when (-> resp :status set (contains? "need-input")) (session {:op :stdin :stdin ":ohai\n"})) resp))))) (session {:op :stdin :stdin "a\n"}) (is (= ["a"] (repl-values session "(read-line)")))) (def-repl-test request-multiple-read-with-buffered-newline-*in* (is (= '(:ohai) (response-values (for [resp (repl-eval session "(read)")] (do (when (-> resp :status set (contains? "need-input")) (session {:op :stdin :stdin ":ohai\na\n"})) resp))))) (is (= ["a"] (repl-values session "(read-line)")))) (def-repl-test request-multiple-read-objects-*in* (is (= '(:ohai) (response-values (for [resp (repl-eval session "(read)")] (do (when (-> resp :status set (contains? "need-input")) (session {:op :stdin :stdin ":ohai :kthxbai\n"})) resp))))) (is (= [" :kthxbai"] (repl-values session "(read-line)")))) (def-repl-test test-url-connect (with-open [conn (url-connect (str "nrepl://localhost:" (:port *server*)))] (transport/send conn {:op :eval :code "(+ 1 1)"}) (is (= [2] (response-values (response-seq conn 100)))))) (deftest test-ack (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 (:port s))] (is (= (:port s2) (ack/wait-for-ack 10000)))))) (def-repl-test agent-await (is (= [42] (repl-values session (code (let [a (agent nil)] (send a (fn [_] (Thread/sleep 1000) 42)) (await a) @a))))))