Skip to content

Commit b42b9d0

Browse files
authored
Merge pull request #37 from hlship/hls/20250813-transformer
Transformers - support adding extra commands after namespaces have been scanned
2 parents 7473e2c + 2cadc2e commit b42b9d0

21 files changed

Lines changed: 340 additions & 185 deletions

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@
1414
* The two-arg variant of `print-errors` has been removed
1515
* `dispatch*` function arguments have changed
1616
* `expand-dispatch-options` has been removed
17+
* When an arg is ambiguous during dispatch, the error text now says "could match" and uses "or" as the conjunction, e.g. "ex could match exhume or extract"
18+
* Tool and command help is now printed to \*out\*, not \*err*\*
1719

1820
*Changes*
1921

2022
* Groups may now be nested, to arbitrary depth
2123
* You may now enter `-h` or `--help` after a group to get help for just that group
2224
* Tool help output has been reordered, with top-level tool commands first (previously, those were in a "Builtin" group and listed last)
2325
* Tool help now displays just top-level commands by default (add --full to list nested commands)
26+
* When extracting the first sentence as the single-line index, embedded period are no longer considered the end of the sentence
2427
* net.lewisship.cli-tools
2528
* New `command-path` function returns a composed string of the tool name and command path
2629
* `dispatch` function has new options:
2730
* :handler is a function to handle top-level tool options (then delegate to `dispatch*`)
31+
* :transformer provides a function to add additional commands and groups after namespaces are loaded
32+
* :source-dirs specifies extra directories to consider when caching
2833

2934
# 0.15.1 -- 27 Jan 2025
3035

src/net/lewisship/cli_tools.clj

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -175,19 +175,21 @@
175175
"Expanded dispatch options into tool options, leveraging a cache."
176176
[options]
177177
(let [{:keys [cache-dir]} options
178-
options' (select-keys options [:tool-name
179-
:doc
180-
:namespaces
181-
:groups])
182-
cache-dir' (when cache-dir
183-
(fs/expand-home cache-dir))
184-
digest (when cache-dir'
185-
(cache/classpath-digest options'))
186-
cached (when digest
187-
(cache/read-from-cache cache-dir' digest))
188-
result (if cached
189-
cached
190-
(let [expanded (impl/expand-tool-options options')]
178+
;; Only include the options that should be part of the digest.
179+
digest-options (select-keys options [:tool-name
180+
:doc
181+
:namespaces
182+
:groups
183+
:source-dirs])
184+
cache-dir' (when cache-dir
185+
(fs/expand-home cache-dir))
186+
digest (when cache-dir'
187+
(cache/classpath-digest digest-options))
188+
cached (when digest
189+
(cache/read-from-cache cache-dir' digest))
190+
result (if cached
191+
cached
192+
(let [expanded (impl/expand-tool-options options)]
191193
(when cache-dir'
192194
(cache/write-to-cache cache-dir' digest expanded))
193195
expanded))]
@@ -271,6 +273,8 @@
271273
- :groups - map of group command (a string) to a group map
272274
- :handler - function to handle tool-level options, defaults to [[default-tool-handler]]
273275
- :cache-dir (optional, string) - directory to cache data in, or nil to disable cache
276+
- :transformer (optional, function) - transforms the root command map
277+
- :source-dirs (optional, seq of strings) - additional directories related to caching
274278
275279
The :tool-name option is only semi-optional; in a Babashka script, it will default
276280
from the `babashka.file` system property, if any. An exception is thrown if :tool-name
@@ -294,6 +298,14 @@
294298
be loaded. :cache-dir defaults to the value of the CLI_TOOLS_CACHE_DIR environment variable, or
295299
to the default value `~/.cli-tools-cache`. If set to nil, then caching is disabled.
296300
301+
The :source-dirs option is typically used with the :transformer option; the source directories are
302+
additional directories whose contents should be included by the cache digest (because the transformer
303+
reads files in those directories).
304+
305+
The transformer function is passed the dispatch options and the root commands map and returns
306+
an updated commands map; typically this involves identifying additional commands and adding them
307+
via [[inject-command]].
308+
297309
Returns nil."
298310
[dispatch-options]
299311
(let [options' (merge {:arguments *command-line-args*}
@@ -496,3 +508,52 @@
496508
:value true}
497509
{:label "no"
498510
:value false}])
511+
512+
(defn selected-command
513+
"Returns a map defining the selected command. This will be nil until the end of
514+
dispatch (i.e., just before the command function is invoked). This can be used
515+
in the implementation of commands not defined by [[defcommand]]."
516+
{:added "0.16.0"}
517+
[]
518+
impl/*command-map*)
519+
520+
(defn- inject
521+
[commands-map remaining-command-path path-to-here command-map]
522+
(let [command (first remaining-command-path)
523+
remaining' (next remaining-command-path)
524+
path' (conj path-to-here command)]
525+
(cond
526+
(nil? remaining')
527+
(assoc commands-map command command-map)
528+
529+
(contains? commands-map command)
530+
(update-in commands-map [command :subs]
531+
inject remaining' path' command-map)
532+
533+
:else
534+
(assoc commands-map command
535+
{:command-path path'
536+
:command command
537+
:subs (inject {} remaining' path' command-map)}))))
538+
539+
(defn inject-command
540+
"Injects a new command into the command root; presumably one not defined via
541+
[[defcommand]].
542+
543+
command-path - a seq of command name strings leading to the new command
544+
command-map - map that defines the command
545+
546+
The final term of the command path is the name of the command itself.
547+
548+
A command map has keys:
549+
- :fn (keyword, required) - identifies function to invoke
550+
- :doc (string, required) - long description of the command
551+
- :title (string, optional) - short description of the command
552+
"
553+
{:added "0.16.0"}
554+
[command-root command-path command-map]
555+
(let [command-map' (assoc command-map
556+
:command (last command-path)
557+
:command-path command-path)]
558+
(inject command-root command-path [] command-map')))
559+

src/net/lewisship/cli_tools/cache.cljc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,18 @@
6262
(str sb)))
6363

6464
(defn classpath-digest
65-
"Passed opts, return a hex string of the SHA-1 digest of the opts and the classpath."
66-
^String [opts]
67-
(let [digest (MessageDigest/getInstance "SHA-1")
65+
"Passed digest options, return a hex string of the SHA-1 digest of the opts and the classpath."
66+
^String [digest-options]
67+
(let [{:keys [source-dirs]} digest-options
68+
digest (MessageDigest/getInstance "SHA-1")
6869
;; The options determine critical things such as the namespaces that
6970
;; will be scanned for defcommands.
7071
digest-bytes (do
71-
(update-digest-from-string digest (pr-str opts))
72+
(update-digest-from-string digest (pr-str digest-options))
7273
(run! #(update-digest digest %) (get-split-classpath))
74+
(->> source-dirs
75+
(map fs/file)
76+
(run! #(update-digest-recursively digest %)))
7377
(.digest digest))]
7478
(hex-string digest-bytes)))
7579

0 commit comments

Comments
 (0)