• Stars
    star
    661
  • Rank 65,833 (Top 2 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created over 13 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Clojure network REPL that provides a 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.

nREPL

This project's development moved to a new nrepl/nrepl repository, outside of Clojure Contrib, after version 0.2.13

nREPL is a Clojure network 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:

[org.clojure/tools.nrepl "0.2.13"]

Or, add this to your Maven project's pom.xml:

<dependency>
  <groupId>org.clojure</groupId>
  <artifactId>tools.nrepl</artifactId>
  <version>0.2.13</version>
</dependency>

A list of all prior releases are available here.

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 or clojure-tools mailing lists. Bug reports and such may be filed into nREPL's JIRA.

nREPL's generated API documentation is available here. A history of nREPL builds is available, as well as a compatibility 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:

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:

=> (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:

=> (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.

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, and add code like this to your app:

=> (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:

=> (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.

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 for how to configure Leiningen or Maven to use OSS-snapshots), so building nREPL shouldn't ever be necessary. But, if you insist:

  1. Clone the repo
  2. Make sure you have maven installed
  3. 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, and the notes and discussion 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, 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:

{"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 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 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:

(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 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:

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

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

More Repositories

1

clojure

The Clojure programming language
Java
10,259
star
2

clojurescript

Clojure to JS compiler
Clojure
9,164
star
3

core.async

Facilities for async programming and communication in Clojure
Clojure
1,933
star
4

clojure-clr

A port of Clojure to the CLR, part of the Clojure project
C#
1,531
star
5

core.logic

A logic programming library for Clojure & ClojureScript
Clojure
1,430
star
6

core.typed

An optional type system for Clojure
Clojure
1,280
star
7

core.match

An optimized pattern matching library for Clojure
Clojure
1,170
star
8

test.check

QuickCheck for Clojure
Clojure
1,109
star
9

java.jdbc

JDBC from Clojure (formerly clojure.contrib.sql)
Clojure
713
star
10

tools.cli

Command-line processing
Clojure
701
star
11

tools.namespace

Tools for managing namespaces in Clojure
Clojure
593
star
12

data.json

JSON in Clojure
Clojure
531
star
13

algo.monads

Macros for defining monads, and definition of the most common monads
Clojure
442
star
14

tools.deps.alpha

A functional API for transitive dependency graph expansion and the creation of classpaths
Clojure
435
star
15

core.cache

A caching library for Clojure implementing various cache strategies
Clojure
433
star
16

tools.logging

Clojure logging API
Clojure
380
star
17

tools.trace

1.3 update of clojure.contrib.trace
Clojure
355
star
18

math.combinatorics

Efficient, functional algorithms for generating lazy sequences for common combinatorial functions
Clojure
343
star
19

spec-alpha2

Clojure library to describe the structure of data and functions
Clojure
296
star
20

data.csv

CSV reader/writer to/from Clojure data structures
Clojure
266
star
21

core.memoize

A manipulable, pluggable, memoization framework for Clojure
Clojure
260
star
22

tools.analyzer

An analyzer for Clojure code, written in Clojure and producing AST in EDN
Clojure
255
star
23

clojure-site

clojure.org site
HTML
244
star
24

data.xml

Clojure
219
star
25

data.finger-tree

Finger Tree data structure
Clojure
213
star
26

spec.alpha

Clojure library to describe the structure of data and functions
Clojure
211
star
27

tools.reader

Clojure reader in Clojure
Clojure
203
star
28

tools.build

Clojure builds as Clojure programs
Clojure
193
star
29

core.rrb-vector

RRB-Trees in Clojure
Clojure
190
star
30

data.priority-map

Clojure priority map data structure
Clojure
186
star
31

math.numeric-tower

Math functions that deal intelligently with the various types in Clojure's numeric tower
Clojure
176
star
32

test.generative

Generative test runner
Clojure
161
star
33

core.unify

Unification library
Clojure
136
star
34

core.contracts

Contracts programming
Clojure
128
star
35

data.fressian

Read and write Fressian data from Clojure
Clojure
126
star
36

data.avl

Persistent sorted maps and sets with log-time rank queries
Clojure
124
star
37

data.int-map

A map optimized for integer keys
Java
124
star
38

core.incubator

Proving ground for proposed new core fns
Clojure
116
star
39

tools.macro

Utilities for macro writers
Clojure
114
star
40

java.data

Functions for recursively converting Java beans to Clojure and vice versa
Clojure
113
star
41

tools.analyzer.jvm

Additional jvm-specific passes for tools.analyzer
Clojure
112
star
42

clojurescript-site

website for ClojureScript
Shell
106
star
43

tools.deps.graph

Dependency graphs for deps.edn projects
Clojure
102
star
44

java.jmx

Produce and consume JMX beans from Clojure
Clojure
93
star
45

algo.generic

Generic versions of commonly used functions, implemented as multimethods that can be implemented for any data type
Clojure
92
star
46

tools.emitter.jvm

A JVM bytecode generator for ASTs compatible with tools.analyzer(.jvm)
Clojure
87
star
47

data.generators

Random data generators
Clojure
85
star
48

data.zip

Utilities for clojure.zip
Clojure
82
star
49

brew-install

Clojure CLI installer
Shell
78
star
50

data.codec

Native codec implementations
Clojure
74
star
51

tools.gitlibs

API for retrieving, caching, and programatically accessing git libraries
Clojure
59
star
52

java.classpath

Examine the Java classpath from Clojure programs
Clojure
58
star
53

jvm.tools.analyzer

Clojure
53
star
54

core.specs.alpha

specs to describe Clojure core macros and functions
Clojure
46
star
55

tools.tools

Clojure CLI tool for managing Clojure CLI tools
Clojure
41
star
56

homebrew-tools

Clojure homebrew tap providing Clojure formulae
Ruby
40
star
57

test.benchmark

Benchmark and Regression Suite for Clojure
Roff
37
star
58

data.alpha.replicant-server

A Clojure library providing remote implementations of the Clojure data structures and a remote REPL server.
Clojure
35
star
59

clr.tools.nrepl

Clojure
26
star
60

build.ci

Support scripts for continuous integration
Clojure
23
star
61

tools.analyzer.js

Provides js-specific passes for tools.analyzer
Clojure
21
star
62

clojure-install

Java
16
star
63

algo.graph

Basic graph theory algorithms
Clojure
15
star
64

data.alpha.replicant-client

A Clojure library providing client-side implementations of Clojure datastructures served by replicant-server.
Clojure
11
star
65

clojure.github.com

Documentation repos
HTML
8
star
66

build.poms

Parent POMs
8
star
67

core.typed.analyzer.jvm

Clojure
7
star
68

clr.tools.namespace

Clojure
7
star
69

core.typed.runtime.jvm

Clojure
7
star
70

clr.data.json

JSON in Clojure on the CLR
Clojure
6
star
71

clr.tools.reader

Clojure
5
star
72

clr.test.generative

Clojure
5
star
73

clojure-api-doc

Clojure API doc build
Clojure
5
star
74

contrib-api-doc

Clojure contrib API doc build
Clojure
5
star
75

core.typed.annotator.jvm

Clojure
5
star
76

core.typed.checker.jvm

Clojure
4
star
77

core.typed.checker.js

Clojure
4
star
78

io.incubator

Proving ground for proposed new io fns
4
star
79

clr.data.generators

Random data generators for Clojure on the CLR
Clojure
4
star
80

clr.core.async

Port of Clojure core.async to the CLR
Clojure
3
star
81

clr.spec.alpha

spec on the CLR
Clojure
3
star
82

clr.tools.analyzer

Clojure
3
star
83

test.regression

Regression tests for Clojure
Clojure
3
star
84

tools.deps.cli

Deps functions
Clojure
2
star
85

clr.core.specs.alpha

core specs on CLR
HTML
2
star
86

clr.tools.gitlibs

An API for retrieving, caching, and programatically accessing git libraries
HTML
2
star
87

java.internal.invoke

2
star
88

clr.core.logic

Clojure
2
star
89

clr.tools.trace

1
star
90

clr.data.priority-map

ClojureCLR port of data.priority-map
Clojure
1
star
91

cljs.tools.closure

ClojureScript build of Google Closure
Shell
1
star
92

tools.analyzer.clr

additional clr-specific passes for tools.analyzer
Clojure
1
star
93

clr.test.check

Clojure
1
star
94

clr.core.cache

ClojureCLR port of core.cache
Clojure
1
star
95

clr.tools.logging

1
star
96

build.test

Dummy project for testing contrib build and deploy
Clojure
1
star
97

clr.core.memoize

ClojureCLR port of core.memoize
Clojure
1
star