Planck Scripting

August 1, 2015

When Planck was first created, the primary purpose was to explore how quickly a bootstrapped ClojureScript REPL could be started on the desktop. But it was also clear that low-latency startup would be great for scripting.




With that in mind, some pragmatic aspects are being developed, specifically in support of scripting, outside of the normal use as a REPL.

Command Line Arguments FTW

All of the command line arguments that are supported by the regular Clojure REPL have been implemented. This lends great flexibility in how you start up Planck, having it load a ClojureScript file prior to anything else (--init) or evaluate a form directly inline (--eval).

You can run a ClojureScript by specifying its path as the first argument to Planck, and you can even have Plank run CloureScript code that is read in via standard input (by passing in - where you would normally specify a path to a file).

And, if your code is a bit more structured, you can call into a -main entry point as is covered in ClojureScript Mainia.

Shebang

As Jack Rusher kindly points out, it is possible to embed a shebang at the beginning of a ClojureScript file in order to use Planck as the interpreter. In other words, with hello.cljs containing

#!/usr/local/bin/planck
(println "Hello world!")

if you make the file executable then, voilà:

$ ./hello.cljs
Hello world!

This works because the reader supports the #! syntax as a comment specifically for this purpose. Woot!

In fact, your shebang scripts can use the full power of ClojureScript's namespace and dependency management. The following riff on ClojureScript Mainia works just fine:

#!/usr/local/bin/planck -c /path/to/src
(ns calculate.core
  (:require [pythag.core :refer [dist]]))

(println (dist 3 4))

Of course, if you need to pass arguments to your script, --main is your friend.

Stream Processing

Planck has slurp and spit, but what if you want to process more data than can fit into memory, or you want to write scripts that can participate in the Unix pipes architecture?

To that end, with the help of Ryan Fowler, Planck has been revised to support read-line:

$ planck
cljs.user=> (require 'planck.io)
nil
cljs.user=> (planck.io/read-line)
abc
"abc"
cljs.user=>

In the above, I typed abc and hit return after calling read-line, and it returned the string "abc".

Given this primitive, let's see if we can write the equivalent of a classic “count distinct lines” pipeline:

cat /etc/services | sort | uniq | wc -l
   13697

First, let me create a little helper function in src/helper/core.cljs to generate a line sequence using read-line:

(ns helper.core 
  (:require planck.io))
(defn line-seq []
  (take-while identity 
    (repeatedly planck.io/read-line)))

Then it is simple to write equivalents of sort, uniq, and wc -l in terms of the core sort, dedupe, and count functions:

sort.cljs:

#!/usr/local/bin/planck
(ns sort.core 
  (:require helper.core))
(run! println 
  (sort (helper.core/line-seq)))

uniq.cljs:

#!/usr/local/bin/planck
(ns uniq.core 
  (:require helper.core))
(run! println 
  (dedupe (helper.core/line-seq)))

wc_l.cljs:

#!/usr/local/bin/planck
(ns wc-l.core 
  (:require helper.core))
(println 
  (count (helper.core/line-seq)))

Now you can construct the pipeline:

cat /etc/services | ./sort.cljs | ./uniq.cljs | ./wc_l.cljs
13697

Of course, there is much more you can typically do with scripts. I'm thinking that porting more of Clojure's I/O facilities and a port of clojure.java.sh would be very useful.

Tags: Planck ClojureScript Bootstrap