Skip to content

A shell nREPL client for boot nREPL and other nREPls.

License

Notifications You must be signed in to change notification settings

christo-auer/bootycall

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bootycall

A shell nREPL client for boot nREPL and other nREPls.

bootycall is based on the [Python nREPL client implementation] (https://github.com/cemerick/nrepl-python-client) by cemerick.

Motivation

The main motivation for this script was to make Clojure code callable from the shell by using [nREPL] (https://github.com/clojure/tools.nrepl). In particular, [boot] (https://github.com/boot-clj/boot) tasks and CLI tasks can be called very fast as the JVM has not to start up for every call.

In the following snippet, bcall makes a call to the task fire (see [here] (http://www.braveclojure.com/appendix-b/)) by sending the call to a running boot nREPL process. This takes about 0.21s (on a mid-2012 Macbook Pro):

% time bcall -v fire --thing pants -p
My pants are on fire!
bcall fire --thing pants -p  0.21s user 0.03s system 92% cpu 0.259 tota

Directly calling boot takes around 7s.

% time boot fire --thing pants -p
My pants are on fire!
boot fire --thing pants -p  6.99s user 0.27s system 255% cpu 2.848 total

The general idea is to make writing Clojure code for the shell more practical by avoiding the JVM startup time that is needed with every call. And even if the final Clojure "Shell" script is called directly, bootycall can be used during development for testing.

Installation

You can either clone this repository and call the Python script bcall.py directly or you can install bootycall via pip:

pip install bootycall

Usage Examples

Basic

Start an nREPL either via Leiningen (lein repl) or via Boot (boot repl). You should see some message that an nREPL server has been started with a nREPL URL of the form nrepl://127.0.0.1:<port>.

With bootycall you can evaluate Clojure code within the REPL context from you shell. Let's define the classic Fibonacci function (replace <port> with the port number of your nREPL):

bcall -p <port> -f << EOF
(defn fib [n]
  (if (<= n 1)
		1
		(+ (-> n dec fib) (-> n dec dec fib))))
EOF

As answer you should get the output produced by the REPL (e.g. #'boot.user/fib in case you used boot).

-p tells bootycall the port to connect to and with -f (form) we specify that we want to send one (or more) forms to the nREPL, the definition of fib in our example. When we would leave -f out, then bootycall assumes that we want to call a boot (CLI) task (see below). The Clojure code is read from stdin (the keyboard in the shell) until EOF is read.

We can now call fib:

bcall -p <port> -f "(fib 10)"

The answer should be 89. Note that this time we passed the form directly as an argument to bootycall. Conversely, when we provide no code bootycall reads it from stdin as seen above.

.nrepl-port and NREPL_URL

When you start an nREPL service then this will create the file .nrepl-port in the current working directory. bootycall uses this file when you don't specify any port via -p. Hence, when you execute the bootycall in the same directory as the nREPL service is running you don't need to specify the port.

Alternatively, you can define the URL to the nREPL via the environment variable NREPL_URL:

export NREPL_URL="nrepl://127.0.0.1:<port>"
bcall -f "(map inc (range 10))"
(1 2 3 4 5 6 7 8 9 10)

Calling Boot (CLI) Tasks

Assume we define the following boot task (taken from [Clojure for the Brave and True by Daniel Higginbotham] (http://www.braveclojure.com/appendix-b/)):

(deftask fire
  "Announces that something is on fire"
  [t thing     THING str  "The thing that's on fire"
   p pluralize       bool "Whether to pluralize"]
  (let [verb (if pluralize "are" "is")]
    (println "My" thing verb "on fire!")))

With boot we could call this task via:

boot fire -t Pants -p
My Pants are on fire!

Which takes some time due to the startup time needed by the JVM. With bootycall you can use exeucte the task faster:

% bcall fire -t Pants -p
My Pants are on fire!
nil

Note that -f is not needed here as we don't pass a form to the nREPL but make a call to boot. Also note that the second output is nil which is the return value of the task. More on the different types of outputs in the next section.

Output and Values

When evaluating via the nREPL, there are two types of output: The stuff written to standard output (e.g. via (println ...)) and the return value generated by the evaluation of the form. (There is also stuff written to standard error which is passed to stderr by bootycall.) For instance, consider the following call to bcall with a Fibonacci function that prints something to the console:

% bcall -f << EOF
(defn fib [n]
  (println (str "(fib " n ")"))
	  (if (<= n 1)
		    1
				(+ (-> n dec fib) (-> n dec dec fib))))
EOF

Executing it produces:

% bcall -f -o "(fib 3)"
(fib 3)
(fib 2)
(fib 1)
(fib 0)
(fib 1)
3

All lines except for the last one are (console) output and the last line is the return value of fib. If we are not interested in the output but only in the values, we can use -o to supress the output:

% bcall -f -o "(fib 3)"
3

Likewise, to supress the value, we use -v:

% bcall -f -v "(fib 3)"
(fib 3)
(fib 2)
(fib 1)
(fib 0)
(fib 1)

Note that passing multiple forms to bootycall produces multiple values:

% bcall -f -o "(fib 3) (fib 4) (fib 5)"
3
5
8

bootycall can also redirect (console) output and values to different file descriptors. The following example redirects the former to the file output and the latter to the file values:

% exec {OUTPUT_FD} > output
% exec {VALUES_FD} > values
% bcall -f -O ${OUTPUT_FD} -V ${VALUES_FD} "(fib 3) (fib 4) (fib 5)"
% cat values
3
5
8

A Warning about the Working Directory

When you start the nREPL service and bootycall from the same directory, then both share the same working directory. In particular, relative paths in each point to the same files. However, when you call bcall from a different directly, then their relative paths to files are different.

% ls
a.txt
% cat a.txt
I am a.txt and contain some text.
% bcall -f '(slurp "a.txt")'
java.io.FileNotFoundException: a.txt (No such file or directory)

We can avoid this problem by calling bootycall in working directory of the nREPL service or by using absolute paths:

% bcall -f "(slurp \"${PWD}/a.txt\")"
"I am a.txt and contain some text.\n"

License

Copyright ©2016 Christopher Auer

Distributed under the MIT License. Please see the LICENSE file at the top level of this repo.

About

A shell nREPL client for boot nREPL and other nREPls.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages