Read project name and version from Leiningen project.clj file

Viewed 469

How can I query the project group, name and version from a Leiningen project.clj file using only lein and simple command line utilities without modifying the project file?

I could not find anything useful in lein help, like executing code from the command line arguments.

I am thinking about writing a sed expression, but I am wondering if there is a simpler way using Leiningen.

5 Answers

The contents of project.clj is a clojure data structure so you can read it e.g.

(let [[_ ga version] (read-string (slurp "project.clj"))]
   [(namespace ga) (name ga) version])

This is fully(?) supported by Leiningen, sort of. In short:

$ (lein update-in :version pr -- version ; lein update-in :name pr -- version) | cut -d '"' -f 2

This works by abusing the update-in command to apply clojure.core/pr to the :version or :name project key. (You can see all project keys by using just a colon as the member to pr. The just-a-colon feature is described by lein help update-in.)

Leiningen requires an actual command too, in addition to update-in. Because pr returns nil, I suppose the updates damage the project map and make it unsuitable for real work. The above demo sidesteps the problem by using the version command to print Leiningen's own version.

Oddly, the printed version or name appears more than once; thus the cut command to take only the first quoted string on each line. We assume you haven't used quotes in your project name or version!

You can stack update-in's on one command line, but doing so makes the name and version harder to parse from the output.

I would just write a Clojure function. Java regular expressions are quite powerful (more than unix grep).

Something like this will get you started:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require [tupelo.string :as str]))

(dotest
  (let [txt    (str/quotes->single (slurp "project.clj"))
        result (re-find
                 #"(?ix)                # turn on case-insensitive (i) and freespace (x) modes
                  defproject\s+         # anchor the regex, followed by 1+ spaces
                  ([-a-z0-9]+)\/        # maven group name, followed by a slash
                  ([-a-z0-9]+)\s+       # maven artifact name, followed by 1+ spaces
                  (\S+)                 # version string
                  "
                 txt)]
    (spyx-pretty result)))

with result:

--------------------------------------
   Clojure 1.10.2-alpha1    Java 15
--------------------------------------

Testing tst.demo.core
result => 
["defproject  demo-grp/demo-art  '0.1.0-SNAPSHOT'"
 "demo-grp"
 "demo-art"
 "'0.1.0-SNAPSHOT'"]

We first convert the double-quotes into single-quotes to simplify the regex and the output. We use a custom pattern class that includes not only alphanumeric chars but also hyphens. Clojure slurp is handy since it returns the file as a single large string, ignoring the separate lines which is a problem for Unix grep if everything of interest is not on the first line.

The above is based on a simple template project.

Feel free to embellish further!

I don't think Leiningen lets you evaluate things on the command line, but it does let you start a REPL to which you could pipe some Clojure code:

$ echo "(-> '$(cat project.clj) (nth 1) namespace)" | lein repl | tail -n2 | head -n1
"mygroup"
$ echo "(-> '$(cat project.clj) (nth 1) name)" | lein repl | tail -n2 | head -n1
"myproject"
$ echo "(-> '$(cat project.clj) (nth 2))" | lein repl | tail -n2 | head -n1
"0.1.0-SNAPSHOT"

But if you want something less bulky, would installing Babashka be an option?

$ bb -i "(-> '$(cat project.clj) (nth 1) namespace)"
"mygroup"
$ bb -i "(-> '$(cat project.clj) (nth 1) name)"
"myproject"
$ bb -i "(-> '$(cat project.clj) (nth 2))"
"0.1.0-SNAPSHOT"

Add a println to omit the quotes:

$ bb -i "(-> '$(cat project.clj) (nth 2) println)"
0.1.0-SNAPSHOT
Related