Command Line Subcommands in Clojure

Here’s the translation of the Go code example to Clojure, formatted in Markdown suitable for Hugo:

Our first program will demonstrate how to create subcommands with their own set of flags, similar to tools like git that have many subcommands.

(ns command-line-subcommands
  (:require [clojure.tools.cli :refer [parse-opts]]
            [clojure.string :as string]))

(def foo-options
  [["-e" "--enable" "Enable"]
   ["-n" "--name NAME" "Name"]])

(def bar-options
  [["-l" "--level LEVEL" "Level"
    :parse-fn #(Integer/parseInt %)
    :validate [#(>= % 0) "Must be a number >= 0"]]])

(defn foo-command [args]
  (let [{:keys [options arguments]} (parse-opts args foo-options)]
    (println "subcommand 'foo'")
    (println "  enable:" (:enable options))
    (println "  name:" (:name options))
    (println "  tail:" (string/join " " arguments))))

(defn bar-command [args]
  (let [{:keys [options arguments]} (parse-opts args bar-options)]
    (println "subcommand 'bar'")
    (println "  level:" (:level options))
    (println "  tail:" (string/join " " arguments))))

(defn -main [& args]
  (if (< (count args) 1)
    (println "expected 'foo' or 'bar' subcommands")
    (case (first args)
      "foo" (foo-command (rest args))
      "bar" (bar-command (rest args))
      (println "expected 'foo' or 'bar' subcommands"))))

To run the program, save it as command_line_subcommands.clj and use the clj command:

$ clj -M command_line_subcommands.clj foo -e -n joe a1 a2
subcommand 'foo'
  enable: true
  name: joe
  tail: a1 a2

Now try the bar subcommand:

$ clj -M command_line_subcommands.clj bar -l 8 a1
subcommand 'bar'
  level: 8
  tail: a1

But bar won’t accept foo’s flags:

$ clj -M command_line_subcommands.clj bar -e a1
Unknown option: "-e"

In this Clojure version, we use the clojure.tools.cli library to parse command-line options. Each subcommand is defined as a separate function with its own set of options. The main function dispatches to the appropriate subcommand based on the first argument.

The structure is similar to the original example, but adapted to Clojure’s functional style. Instead of using mutable flags, we parse the options into a map and pass it to the subcommand functions.

Next, we’ll look at environment variables, another common way to parameterize programs.