Skip to content

Commit 37314ce

Browse files
committed
Make some improvements to the shell completion
1 parent 72290c2 commit 37314ce

5 files changed

Lines changed: 96 additions & 65 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -614,21 +614,21 @@ bones, it can complete subcommands and flags, and is aware of nested
614614
subcommands, but don't expect it to be perfect. There is no smart completion of
615615
flag values yes.
616616

617-
To get started run the hidden `__install_zsh_completions` subcommand. This will
618-
create `~/.zsh/completions/_licli`, and add a section to your `~/.zshrc` to
617+
To get started run the hidden `__licli install-completions` subcommand. This
618+
will create `~/.zsh/completions/_licli`, and add a section to your `~/.zshrc` to
619619
autoload it and associate it with the current command.
620620

621621
e.g.
622622

623623
```
624-
bin/my_command __install_zsh_completions
624+
bin/my_command __licli install-completions
625625
```
626626

627627
When calling from Clojure (not Babashka), pass the full path to the executable
628628
as an argument. You can easily use `realpath` for that.
629629

630630
```
631-
bin/my_command __install_zsh_completions `realpath bin/my_command`
631+
bin/my_command __licli install-completions `realpath bin/my_command`
632632
```
633633

634634
Other shells may come in the future. Contributions to the shell completion are welcome!

resources/lambdaisland/cli/_licli.zsh

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
_licli() {
44
local completion_output
5-
completion_output="$(\
6-
${words[1]} __zsh_completions \
7-
-- "${words[@]}" \
8-
)"
5+
completion_output="$( ${words[1]} __licli completions -- "${words[@]}" )"
96

107
if [ -n "$completion_output" ]; then
118
local -a pairs

src/lambdaisland/cli.clj

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[clojure.pprint :as pprint]
88
[clojure.set :as set]
99
[clojure.string :as str]
10+
[lambdaisland.cli.cmdspec :as cmdspec]
1011
[lambdaisland.cli.completions :as completions]))
1112

1213
;; I've tried to be somewhat consistent with variable naming
@@ -207,49 +208,19 @@
207208
(map #(str "-" %) (next flag))
208209
[flag])))
209210

210-
(def args-re #" ([A-Z][A-Z_-]*)|[= ]<([^>]+)>")
211211
(def flag-re #"^(--?)(\[no-\])?(.+)$")
212212

213-
(defn parse-arg-names [str]
214-
[(str/split (str/replace str args-re "") #",\s*")
215-
(str/join (map first (re-seq args-re str)))
216-
(mapv (fn [[_ u l]] (keyword (str/lower-case (or u l)))) (re-seq args-re str))])
217-
218-
(defn to-cmdspec [?var]
219-
(cond
220-
(var? ?var)
221-
(if (fn? @?var)
222-
(assoc (meta ?var) :command ?var)
223-
(merge (meta ?var) @?var))
224-
225-
(fn? ?var)
226-
{:command ?var}
227-
228-
(var? (:command ?var))
229-
(merge (meta (:command ?var)) ?var)
230-
231-
:else
232-
?var))
233-
234-
(defn prepare-cmdpairs [commands]
235-
(let [m (if (vector? commands) (partition 2 commands) commands)]
236-
(map (fn [[k v]]
237-
(let [v (to-cmdspec v)
238-
[[cmd] doc argnames] (parse-arg-names k)]
239-
[cmd (assoc v :argdoc doc :argnames argnames)]))
240-
m)))
241-
242213
(defn cmd->flags [cmdspec args]
243214
(if (seq args)
244215
(when-let [cmds (:commands cmdspec)]
245-
(cmd->flags (get (into {} (prepare-cmdpairs (:commands cmdspec)))
216+
(cmd->flags (get (into {} (cmdspec/prepare-cmdpairs (:commands cmdspec)))
246217
(first args))
247218
(rest args)))
248219
(:flags cmdspec)))
249220

250221
(defn parse-flagstr [flagstr flagopts]
251222
(let [;; support "--foo=<hello>" and "--foo HELLO"
252-
[flags argdoc argnames] (parse-arg-names flagstr)
223+
[flags argdoc argnames] (cmdspec/parse-arg-names flagstr)
253224
argcnt (count argnames)
254225
;; e.g. "-i,--input, --[no-]foo ARG"
255226
flagstrs (map #(re-find flag-re %) flags)
@@ -403,7 +374,7 @@
403374

404375
(defn dispatch*
405376
([cmdspec]
406-
(dispatch* (to-cmdspec cmdspec) *command-line-args*))
377+
(dispatch* (cmdspec/to-cmdspec cmdspec) *command-line-args*))
407378
([{:keys [flags init] :as cmdspec} cli-args]
408379
(let [init (if (or (fn? init) (var? init)) (init) init)
409380
init (assoc init ::sources (into {} (map (fn [k] [k "Initial context"])) (keys init)))
@@ -422,7 +393,7 @@
422393
(instance? clojure.lang.MultiFn middleware))
423394
[middleware]
424395
middleware))
425-
command-pairs (when commands (prepare-cmdpairs commands))
396+
command-pairs (when commands (cmdspec/prepare-cmdpairs commands))
426397
command-map (when commands (update-keys (into {} command-pairs)
427398
#(first (str/split % #"[ =]"))))]
428399
(cond
@@ -485,18 +456,18 @@
485456
(:doc command-match)
486457
doc)
487458
(for [[k v] (if command-match
488-
(-> command-match :commands prepare-cmdpairs)
459+
(-> command-match :commands cmdspec/prepare-cmdpairs)
489460
command-pairs)]
490461
[k (if (:commands v)
491-
(update v :commands prepare-cmdpairs)
462+
(update v :commands cmdspec/prepare-cmdpairs)
492463
v)])
493464
argnames
494465
flagpairs)
495466
(print-help program-name
496467
doc
497468
(for [[k v] command-pairs]
498469
[k (if (:commands v)
499-
(update v :commands prepare-cmdpairs)
470+
(update v :commands cmdspec/prepare-cmdpairs)
500471
v)])
501472
argnames
502473
flagpairs)))

src/lambdaisland/cli/cmdspec.clj

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
(ns lambdaisland.cli.cmdspec
2+
"Some of the command spec parsing extracted so it can be reused in
3+
lambdaisland.cli.completions"
4+
(:require
5+
[clojure.string :as str]))
6+
7+
(def args-re #" ([A-Z][A-Z_-]*)|[= ]<([^>]+)>")
8+
9+
(defn parse-arg-names [str]
10+
[(str/split (str/replace str args-re "") #",\s*")
11+
(str/join (map first (re-seq args-re str)))
12+
(mapv (fn [[_ u l]] (keyword (str/lower-case (or u l)))) (re-seq args-re str))])
13+
14+
(defn to-cmdspec [?var]
15+
(cond
16+
(var? ?var)
17+
(if (fn? @?var)
18+
(assoc (meta ?var) :command ?var)
19+
(merge (meta ?var) @?var))
20+
21+
(fn? ?var)
22+
{:command ?var}
23+
24+
(var? (:command ?var))
25+
(merge (meta (:command ?var)) ?var)
26+
27+
:else
28+
?var))
29+
30+
(defn prepare-cmdpairs [commands]
31+
(let [m (if (vector? commands) (partition 2 commands) commands)]
32+
(map (fn [[k v]]
33+
(let [v (to-cmdspec v)
34+
[[cmd] doc argnames] (parse-arg-names k)]
35+
[cmd (assoc v :argdoc doc :argnames argnames)]))
36+
m)))
Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
(ns lambdaisland.cli.completions
22
(:require
3+
[lambdaisland.cli.cmdspec :as cmdspec]
34
[clojure.java.io :as io]
45
[clojure.string :as str]))
56

7+
(declare with-completions)
8+
69
(defn zsh-completions [cmdspec {:lambdaisland.cli/keys [argv] :as opts}]
710
(let [cmdspec (reduce
811
(fn [cmdspec arg]
9-
(let [commands ((resolve 'lambdaisland.cli/prepare-cmdpairs) (:commands cmdspec))]
12+
(let [commands (cmdspec/prepare-cmdpairs (:commands (with-completions cmdspec)))]
1013
(if-let [command-match (get (into {} commands) arg)]
1114
(-> cmdspec
1215
(dissoc :command :commands :middleware)
1316
(merge command-match)
14-
(assoc :flagmap (merge (:flagmap ((resolve 'lambdaisland.cli/to-cmdspec) cmdspec))
15-
(:flagmap ((resolve 'lambdaisland.cli/to-cmdspec) command-match)))))
17+
(assoc :flagmap (merge (:flagmap (cmdspec/to-cmdspec cmdspec))
18+
(:flagmap (cmdspec/to-cmdspec command-match)))))
1619
cmdspec)))
1720
cmdspec
1821
(butlast (next argv)))]
19-
(doseq [[cmd {:keys [doc]}] ((resolve 'lambdaisland.cli/prepare-cmdpairs) (:commands cmdspec))]
20-
(println (str cmd (when doc ":") doc)))
22+
(doseq [[cmd {:keys [doc]}] (cmdspec/prepare-cmdpairs (:commands cmdspec))]
23+
(println (str cmd (when doc ":") (first (str/split doc #"\R")))))
2124
(doseq [[flag {:keys [doc]}] (:flagmap cmdspec)]
2225
(println (str flag ":" (or doc (str/replace flag #"^-+" "")))))))
2326

@@ -32,27 +35,51 @@
3235
(println "e.g. bin/my_script __install_zsh_completions /full/path/to/bin/my_script")
3336
(System/exit -1))
3437
(when-not (.exists cdir) (.mkdirs cdir))
38+
(println "Creating" (str (io/file cdir "_licli")))
3539
(spit (io/file cdir "_licli")
3640
(slurp (io/resource "lambdaisland/cli/_licli.zsh")))
3741
;; we could get a lot fancier here with detecting the block we previously
3842
;; added, but this is an ok start
39-
(spit zshrc-path
40-
(str zshrc
41-
"\n"
42-
(when-not (re-find #"(?m)^fpath=.*\~/.zsh/completions" zshrc)
43-
(str "\n# lambdaisland.cli completions\nfpath=(~/.zsh/completions $fpath)\n"))
44-
(when-not (re-find #"(?m)^autoload -Uz compinit$" zshrc)
45-
(str "autoload -Uz compinit\n"))
46-
(when-not (re-find #"(?m)^compinit$" zshrc)
47-
(str "compinit\n"))
48-
(let [base-name (last (str/split script-name #"/"))]
49-
(str "compdef _licli " script-name " */" base-name " " base-name))))))
43+
(let [stanza (str
44+
(when-not (re-find #"(?m)^fpath=.*\~/.zsh/completions" zshrc)
45+
(str "\n# lambdaisland.cli completions\nfpath=(~/.zsh/completions $fpath)\n"))
46+
(when-not (re-find #"(?m)^autoload -Uz compinit$" zshrc)
47+
(str "autoload -Uz compinit\n"))
48+
(when-not (re-find #"(?m)^compinit$" zshrc)
49+
(str "compinit\n"))
50+
(let [base-name (last (str/split script-name #"/"))]
51+
(str "compdef _licli " script-name " */" base-name " " base-name)))]
52+
(println "Updating" (str zshrc-path) ", adding:")
53+
(println stanza)
54+
(spit zshrc-path (str zshrc "\n" stanza)))))
55+
56+
(defn install-bash-completions [opts]
57+
(throw (ex-info {} "Not implemented"))
58+
)
59+
60+
(defn install-completions
61+
"Do the necessary setup to get shell completions. Shell will be guessed from
62+
$SHELL, unless explicitly specified."
63+
{:flags ["--zsh" "Do zsh setup"
64+
"--bash" "Do bash setup"]}
65+
[opts]
66+
(let [shell (cond
67+
(:zsh opts) :zsh
68+
(:bash opts) :bash
69+
:else (keyword (last (str/split (System/getenv "SHELL") #"/"))))]
70+
(case shell
71+
:zsh (install-zsh-completions opts)
72+
:bash (install-bash-completions opts))))
5073

5174
(defn with-completions [cmdspec]
5275
(update cmdspec
5376
:commands
5477
(fnil into [])
55-
["__zsh_completions" {:no-doc true
56-
:command (partial zsh-completions cmdspec)}
57-
"__install_zsh_completions" {:no-doc true
58-
:command #'install-zsh-completions}]))
78+
["__licli"
79+
{:no-doc true
80+
:commands
81+
["completions" {:doc "Generate completions based on partially completed arguments
82+
83+
called by shell functions to do the actual completing."
84+
:command (partial zsh-completions cmdspec)}
85+
"install-completions" #'install-completions]}]))

0 commit comments

Comments
 (0)