It's rather a rhetorical question as I've come to understand that tools.cli is not capable of doing anything with positional arguments but passing them on to the consumer of the library as strings in a vector. But I'll share my usecase.
I'm writing a tool that copies files from one directory to another. It takes two positional arguments, the source and destination. Right now my code contains two passes of input verification and parsing, first one for options with tools.cli and then a second one for arguments in my own function parse-arguments
. It would be neat if these two things could both be done with in one step.
See the below code for a usage example.
(ns multi-machine-rsync.cli
(:require [clojure.tools.cli :as cli]
[clojure.java.io :as io]
[clojure.string :as string]))
(defn read-settings
[path]
(read-string (slurp path)))
(def cli-options
[["-h" "--help" "Show this help text"]
["-s" "--settings SETTING-FILE" "Path to the settings file."
:parse-fn read-settings
:default "settings.edn"]])
(defn usage
[options-summary]
(string/join
\newline
["Copies a directory from one place to another in a common"
"directory structure using multiple machines."
""
"Usage: multi-machine-rsync [OPTIONS] SOURCE DESTINATION"
""
"Options:"
options-summary]))
(defn parse-arguments
"Parse and validate command line arguments. Either return a map
indicating the program should exit (with a error message, and
optional ok status), or a map indicating the action the program
should take and the options provided."
[args]
(let [{:keys [arguments options errors summary]} (cli/parse-opts args cli-options)
[source destination] (map io/file arguments)]
(cond
(:help options) {:exit-message (usage summary) :ok? true}
errors {:exit-message errors}
(nil? source) {:exit-message "First positional argument SOURCE not given."}
(nil? destination) {:exit-message "Second positional argument DESTINATION not given."}
(not (.exists source)) {:exit-message "Source directory does not exist."}
(not (.exists destination)) {:exit-message "Destination directory does not exist."}
:else {:options options
:source source
:destination destination})))