OSS updates of July - August 2022

In this post I'll give updates about open source I worked on during May and June 2022.

Sponsors

But first off, I'd like to thank all the sponsors and contributors that make this work possible! Top sponsors: Clojurists Together, Roam Research, Nextjournal, Adgoji, Cognitect, Kepler16, .

If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work via the following organizations:

Projects

ClavaScript

This is a new project: a CLJS syntax to JS compiler for the niche use case where you want to write JS, but do it using CLJS syntax and tooling instead. ClavaScript comes with a standard library that resembles CLJS but is built on bare JS ingredients. As such, Clava comes with the usual JS caveats, but we can still have our parens and enjoy a slim bundle size!

Cherry

Cherry is similar to ClavaScript, but it does emit CLJS-compatible code (with the persistent data structures, etc). The compiler code is almost identical to Clava's, but with a few tweaks here and there. E.g. {:a 1} in Clava means: a JS object with a "a" key and 1 value, but in cherry, {:a 1} means the same as in CLJS. The goal of both Clava and Cherry are to reduce friction between CLJS and JS tooling. Both projects should be considered experimental for now. Challenges in both Clava and Cherry is the REPL, since both projects compile to ES6 modules and ES6 module imports are immutable.

On ClojureDays 2022 I will give a talk titled "ClojureScript reimagined" which will shed more light on both projects.

Scittle

Execute Clojure(Script) directly from browser script tags via SCI. See it in action.

Scittle received two new plugins: one for promesa.core and one for cljs.pprint. Also error messages were improved.

Babashka toolbox

Babashka toolbox is a port of Clojure toolbox and gives an overview of bb-compatible libraries and projects.

Babashka CLI

Turn Clojure functions into CLIs!

Several new options have been added: :validate, :require, :restrict. Also error handling was made more flexible.

Babashka CLI proper is now part of babashka. Also see my blog posts about it:

Babashka

Native, fast starting Clojure interpreter for scripting.

Nbb

Scripting in Clojure on Node.js using SCI

Clj-kondo

Static analyzer and linter for Clojure code that sparks joy

Bebo

Run Clojure scripts on Deno via SCI. I'm not exactly sure how useful this is to the wider Clojure community, but I got curious about deno and decided to give this a go.

Quickblog

Light-weight static blog engine for Clojure and babashka

A lot has been happening in this project, with the help of Josh Glover. Check out the changelog. The blog you're currently reading is made with quickblog.

SCI

Configurable Clojure interpreter suitable for scripting and Clojure DSLs.

This is the workhorse that powers babashka, nbb, bebo, and many other projects.

Several bugfixes and enhancements were made in the last two months.

Neil

A CLI to add common aliases and features to deps.edn-based projects.

Neil now has a new subcommand which defers to deps-new. Also neil test was added to run tests using the Cognitect-labs test runner. Much thanks to rads who contributed a lot.

Process

Clojure library for shelling out / spawning subprocesses

Minor updates and fixes.

Fs

File system utility library for Clojure.

Minor updates and fixes.

Pod-babashka-buddy

A babashka pod around buddy core (Cryptographic Api for Clojure).

The latest release adds wrappers for buddy.sign.jwt and provides an aarch64 binary.

See changelogs.

Dynaload

The dynaload logic from clojure.spec.alpha as a library

This library was made compatible with nbb.

Deps.clj

Upgrades and minor fixes.

Sci.configs

A collection of ready to be used SCI configs

Moved cljs.pprint config from nbb into this project.

Discuss this post here.

Published: 2022-09-01

Tagged: clojure oss updates

Babashka tasks meets babashka CLI

In a previous blog post (link) I introduced babashka CLI. It offers you similar benefits as clojure -X but with a more Unixy command line interface.

In version 0.9.160 of babashka, babashka CLI was integrated. It is available as a built-in library so you don't need to declare it anymore in :deps in bb.edn unless you want to use a newer version than the built-in one.

-x

For invoking functions from the command line, you can use the new -x flag (a pun to Clojure's -X of course!):

bb -x clojure.core/identity --hello there
{:hello "there"}

What we see in the above snippet is that a map {:hello "there"} is constructed by babashka CLI and then fed to the identity function. After that the result is printed to the console.

What if we want to influence how things are parsed by babashka CLI and provide some defaults? This can be done using metadata. Let's create a bb.edn and make a file available on the classpath:

bb.edn:

{:paths ["."]}

tasks.clj:

(ns tasks
  {:org.babashka/cli {:exec-args {:ns-data 1}}})

(defn my-function
  {:org.babashka/cli {:exec-args {:fn-data 1}
                      :coerce {:num [:int]}
                      :alias {:n :num}}}
  [m]
  m)

Now let's invoke:

$ bb -x tasks/my-function -n 1 2
{:ns-data 1, :fn-data 1, :num [1 2]}

As you can see, the namespace options are merged with the function options. Defaults can be provided with :exec-args, like you're used to from the clojure CLI.

Tasks

What about task integration? Let's adapt our bb.edn:

{:paths ["."]
 :tasks {doit {:task (let [x (exec 'tasks/my-function)]
                       (prn :x x))
               :exec-args {:task-data 1234}}
         }}

and invoke the task:

$ bb doit --cli-option :yeah -n 1 2 3
:x {:ns-data 1, :fn-data 1, :task-data 1234, :cli-option :yeah, :num [1 2 3]}

As you can see it works similar to -x, but you can provide another set of defaults on the task level with :exec-args. Executing a function through babashka CLI is done using the babashka.task/exec function, available by default in tasks.

Hope you will enjoy this!

Discuss this post here.

Published: 2022-08-02

Tagged: clojure

New Clojure project quickstart

Clojure beginners sometimes struggle with setting up a new Clojure deps.edn project compared to setting up a lein project. This is one of the reasons I've built neil. But not only for beginners, I've been using neil myself a ton too, to add common features to existing deps.edn projects. You may think that neil is a pun on lein. Of course it is. But the name is also an hommage to Neil Peart, one of the greatest progressive rock drummers to have ever lived.

The intent of this post is to give you a starting point from where you can figure out things further. This post isn't going to explain any details of how deps.edn and related tooling works. For that I'm going to refer you to here.

Let's install neil which is available for brew, scoop (Windows), nix, Clojure JVM or can easily be installed manually. Unless you use clojure JVM, neil runs with babashka for fast startup time.

If you've already installed babashka (perhaps indirectly by installing neil) but didn't yet install the clojure CLI or have problems doing so, then you can run bb clojure instead of clojure for launching Clojure. Instead of clj, on linux/macOS you'll want to use rlwrap bb clojure. If you are on Windows and struggle with the official clojure Powershell-based installation, bb clojure may come in handy too.

New

To start a new Clojure project, run neil new --name myproject. This produces a myproject directory with a very basic project layout based on the deps-new scratch template. Now we are are going to incrementally add some functionality to this project. If you like to skip most of these steps, you can start from the more fully featured app template with neil new app --name myproject.

Adding library

Let's decide that we're going to need a library to deal with files. Let's search for one:

$ neil dep search "file system"
:lib fs/fs :version 1.3.3 :description "File system utilities for clojure"
:lib me.raynes/fs :version 1.4.6 :description "File system utilities for clojure"
:lib babashka/fs :version 0.1.6 :description "Babashka file system utilities."

Let's go with the babashka/fs library:

$ neil dep add :lib babashka/fs :version 0.1.6

Now the library is added to deps.edn and we can use it in our project:

$ clj
Clojure 1.11.0
user=> (require '[babashka.fs :as fs])
nil

Test

Let's start by adding a test runner. Type: neil add test. After doing this, you will be able to run:

clojure -M:test

We don't have any tests in this project, so let's add one by adding a file:

test/myproject/core_test.clj

(ns myproject.core-test
  (:require [clojure.test :as t :refer [deftest is testing]]))

(deftest failing-test
  (testing "TODO: fix test"
    (is (= 3 4))))

Now run clojure -M:test again:

$ clojure -M:test

Running tests in #{"test"}

Testing myproject.core-test

FAIL in (failing-test) (core_test.clj:6)
TODO: fix test
expected: (= 3 4)
  actual: (not (= 3 4))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.

The test runner we added is the Cognitect Labs test-runner so check out the README of that project if you need to know more.

Update 2022-08-06: neil now comes with a new subcommand: test, so you can replace clojure -M:test with neil test. Also when you've created a project with neil new, neil add test will generate one default test for you.

REPL

Run:

neil add nrepl

to add a :nrepl alias to your project. Now you can run clj -M:nrepl to get a console REPL, but also a running nREPL server that you can connect to from your editor. Note that many editors also support jack-in and if you prefer to use that, you won't need this.

Uberjar

What's the equivalent of lein uberjar in the deps.edn world? You're going to need tools.build. To create a build.clj file (the program that is going to build your uberjar), run:

neil add build

Since the default build.clj file is going to assume your project is under git version control, let's initialize a git repo first:

git init
git add deps.edn src test
git commit -m "initial commit"

Before creating the uberjar, we have to add :gen-class to src/scratch.clj:

(ns scratch
  (:gen-class))

and we add :main 'scratch in the call to b/uber:

(b/uber {:class-dir class-dir
         :uber-file uber-file
         :basis basis
         :main 'scratch})

Now let's compile the uberjar:

clojure -T:build uber

And then let's run it:

$ java -jar target/lib1-1.2.1-standalone.jar 1 2 3
-main with (1 2 3)

Babashka tasks

If you have difficulty remembering the above invocations, you can write a bb.edn with some tasks:

bb.edn:

{:tasks
 {:requires ([babashka.fs :as fs])

  test {:doc "Run tests"
        :task (apply clojure "-M:test" *command-line-args*)}

  nrepl {:doc "Start REPL"
         :task (if (fs/windows?)
                 (clojure "-M:nrepl")
                 (shell "rlwrap bb clojure -M:nrepl"))}

  uber {:doc "Build uberjar"
        :task (clojure "-T:build uber")}}}

The clojure function is built into babashka and is a drop-in replacement for the clojure CLI which does not require any installation. With

(apply ... *command-line-args*)

we send any args you pass to a task invocation to clojure. So to run a specific test, you can write:

bb test -v myproject.core-test/failing-test

If you prefer to use the installed clojure CLI, you can do this by using:

(apply shell "clojure" ... *command-line-args*)

Now whenever you forget what to do in the current project, run bb tasks:

$ bb tasks
The following tasks are available:

test  Run tests
nrepl Start REPL
uber  Build uberjar

Hope this helps!

Discuss this post here.

Published: 2022-08-02

Tagged: clojure

Archive