Thanks to the recent developments from the ClojureScript community, writing command-line scripts in Clojure has been a fun experience for me. Major kudos to @anmonteiro for developing lumo and the core ClojureScript team.

I think Clojure is a great language for writing single-use scripts to process data because of the built-in manipulation functions and immutable structures so you won’t have to worry about references and deep-cloning.

Running scripts with Lumo

The easiest way to get started is to install lumo on your system and run Clojure files with it (see NPM section below if you don’t want to install lumo globally). Create a file called hello.cljs with the following contents:

(println "Hello World!")

and run it using:

$ npm i -g lumo-cljs ## or other package manager of your choice

$ lumo hello.cljs

Hello World!

Couldn’t be easier. Now let’s look at how we can leverage Node.JS APIs to write a slightly more practical program:

As you can see, the require function can now act like its JS counterpart and pull in JS modules as you would with the following code:

const { writeFileSync } = require('fs');

const inputJson = require('./randomUsers.json');

It also works with modules inside the node_modules folder installed using npm in the same directory (as of lumo 1.8.0 ). U̵n̵f̵o̵r̵t̵u̵n̵a̵t̵e̵l̵y̵,̵ ̵t̵h̵e̵r̵e̵ ̵i̵s̵n̵’̵t̵ ̵a̵n̵ ̵e̵a̵s̵y̵ ̵w̵a̵y̵ ̵t̵o̵ ̵m̵a̵n̵a̵g̵e̵ ̵d̵e̵p̵e̵n̵d̵e̵n̵c̵i̵e̵s̵ ̵o̵n̵ ̵t̵h̵e̵ ̵C̵l̵o̵j̵u̵r̵e̵ ̵s̵i̵d̵e̵ ̵j̵u̵s̵t̵ ̵y̵e̵t̵ ̵s̵o̵ ̵f̵o̵r̵ ̵n̵o̵w̵ ̵w̵e̵ ̵a̵r̵e̵ ̵s̵t̵u̵c̵k̵ ̵w̵i̵t̵h̵ ̵m̵a̵n̵u̵a̵l̵l̵y̵ ̵h̵a̵n̵d̵l̵i̵n̵g̵ ̵J̵A̵R̵ ̵f̵i̵l̵e̵s̵ ̵w̵h̵e̵r̵e̵ ̵C̵l̵o̵j̵u̵r̵e̵ ̵l̵i̵b̵r̵a̵r̵i̵e̵s̵ ̵a̵r̵e̵ ̵u̵s̵u̵a̵l̵l̵y̵ ̵p̵a̵c̵k̵a̵g̵e̵d̵.̵ ̵(̵T̵a̵k̵e̵ ̵a̵ ̵l̵o̵o̵k̵ ̵a̵t̵ ̵t̵h̵e̵ ̵l̵u̵m̵o̵ ̵w̵i̵k̵i̵ ̵f̵o̵r̵ ̵m̵o̵r̵e̵ ̵d̵e̵t̵a̵i̵l̵s̵.̵)̵

Update 2018 Oct: I just found out that it’s possible to use Clojure’s CLI tool clj to manage and set the classpath for Clojure-side dependencies via deps.edn (https://clojure.org/guides/deps_and_cli). In the demo project below, simply replace the lumo flag -c src with -c `clj -Spath` and you should be able to require all the dependencies specified in your deps.edn file.

Integrating with NPM

For slightly larger projects you’d probably want to have a proper package.json with some dependencies on NPM. You could also install lumo on a per-project basis if you don’t want to install it globally or wish to publish the package elsewhere. Here’s a sample project that works like the above example but fetches JSON from https://randomuser.me using the request library from NPM and splits the code into 2 Clojure files with proper name-spacing:

my-tool

|\_ package.json

\_ src

\_ my_tool

|\_ core.cljs

\_ user.cljs

Clojure’s namespace system mirrors the directory structure so the file with the ns my-tool.core must be my_tool/core.cljs .

Gotcha: Hyphen delimited names in the namespace MUST be converted into snake_case in the filesystem.

core.cljs :

user.cljs :

package.json :

The -c flag tells lumo where your source files are and the -m flag specifies which namespace your -main function is in. You can run this tool using the usual npm procedures:

$ npm install

$ npm start 12 ## fetches 12 users and outputs randomUsers.edn

REPL Development

Of course, no Clojure experience would be complete without interactive REPL-based development. Add the following line into the scripts section of your package.json file:

"repl": "lumo -c src -i src/my_tool/core.cljs -n 5777 -r"

The -i flag initializes the REPL with our entry point core.cljs , the -n flag starts a socket REPL on port 5777 for editor integration and finally the -r flag start a REPL in the terminal. With this you could execute arbitrary code in your runtime without loosing state:

$ npm run repl

...

cljs.user=> (in-ns 'my-tool.core) ;; switch to our core namespace

my-tool.core=> (user/parse {:name {:first "john" :last "smith"}})

{:id "c1b61773-133e-434c-afbd-d82b95b814d3",

:username nil,

:password nil,

:email nil,

:full-name "John Smith"}

Final Thoughts

While there are still a few rough edges with the current tooling, I think this is the time where writing CLI scripts in Clojure starts to become a viable option and a nice alternative to JS.

Lumo’s startup time is blazing fast compared to any clojur-y things running on the JVM so it’s a breath of fresh air.

I would definitely recommend trying this out if you enjoy Clojure.

References