Skip to content

Commit 619c205

Browse files
authored
Merge pull request #56 from hlship/hls/20251023-styles
Add support for overridable styles
2 parents 42040c1 + e4db131 commit 619c205

File tree

10 files changed

+93
-40
lines changed

10 files changed

+93
-40
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* Cache files are now stored in `~/.cache/net.lewisship.cli-tools` by default
4343
* Added initial support for commands defined as Babashka CLI functions
4444
* Added `net.lewiship.cli-tools.test` namespace
45+
* Added `net.lewisship.cli-tools.styles` namespace
4546

4647
# 0.15.1 -- 27 Jan 2025
4748

deps.edn

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
:license :asl}
1010

1111
:aliases
12-
{;; clj -X:teBst
12+
{;; clj -X:test
1313
:test
1414
{:extra-paths ["test" "test-resources"]
1515
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1"
@@ -27,14 +27,14 @@
2727
{:extra-deps {org.clojure/tools.cli {:mvn/version "1.2.245" :optional true}
2828
babashka/fs {:mvn/version "0.5.27" :optional true}
2929
babashka/process {:mvn/version "0.6.23" :optional true}
30-
selmer/selmer {:mvn/version "1.12.64" :optional true}
30+
selmer/selmer {:mvn/version "1.12.65" :optional true}
3131
org.babashka/cli {:mvn/version "0.8.66" :optional true}}}
3232

3333
:1.11
3434
{:override-deps {org.clojure/clojure ^:antq/exclude {:mvn/version "1.11.4"}}}
3535

3636
:lint
37-
{:deps {clj-kondo/clj-kondo {:mvn/version "2025.09.22"}}
37+
{:deps {clj-kondo/clj-kondo {:mvn/version "2025.10.23"}}
3838
:main-opts ["-m" "clj-kondo.main" "--lint" "src" "test"]}
3939

4040
:build

doc/cljdoc.edn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
["Babashka CLI" {:file "doc/babashka-cli.md"}]
1414
["Caching" {:file "doc/caching.md"}]
1515
["Transform" {:file "doc/transform.md"}]
16+
["Styles" {:file "doc/styles.md"}]
1617
["Experimental" {:file "doc/experimental.md"}
1718
["Completions" {:file "doc/completions.md"}]
1819
["Job Board" {:file "doc/jobs.md"}]]]}

doc/styles.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Styling cli-tools
2+
3+
Beauty is in the eye of the beholder; `cli-tools` makes a default
4+
set of choices for what kind of colors and fonts to use
5+
for its output, but this can be overridden.
6+
7+
The `net.lewisship.cli-tools.styles` contains a dynamic var, `*default-styles*`, that is used by the rest of `cli-tools`
8+
when
9+
formatting output. By overriding or rebinding this var, the fonts can be overridden.
10+
11+
The most common to override are:
12+
13+
- :tool-name-label (default :bold.green) used when writing the name of the tool itself
14+
- :command-path (default :bold.green) used when writing the command path
15+
16+
When overriding `*default-styles*`, you can just provide overrides of what's in `default-styles`; anything not found in
17+
the dynamic var is then searched for in the non-dynamic var.

src/net/lewisship/cli_tools.clj

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
[clojure.string :as string]
66
[net.lewisship.cli-tools.impl :as impl :refer [cond-let]]
77
[clojure.tools.cli :as cli]
8-
[net.lewisship.cli-tools.cache :as cache]))
8+
[net.lewisship.cli-tools.cache :as cache]
9+
[net.lewisship.cli-tools.styles :refer [style]]))
910

1011
(defn exit
1112
"An indirect call to System/exit, passing a numeric status code (0 for success, non-zero for
@@ -426,7 +427,7 @@
426427

427428
:else
428429
(do
429-
(ansi/perr "Input '" [:yellow input] "' not recognized; enter "
430+
(ansi/perr "Input '" [(style :invalid-input) input] "' not recognized; enter "
430431
(map-indexed
431432
(fn [i {:keys [label]}]
432433
(list
@@ -445,12 +446,12 @@
445446

446447
:else
447448
", or ")
448-
[:italic label]))
449+
[(style :possible-completion) label]))
449450
response-maps)
450451
(when default-label
451452
(list
452453
", or just enter for the default ("
453-
[:bold default-label]
454+
[(style :default-value) default-label]
454455
")")))
455456
(recur))))))))
456457

@@ -602,7 +603,7 @@
602603
console (System/console)
603604
_ (do
604605
(when-not console
605-
(abort [:bold.red "Error:"] " no console to read password from"))
606+
(abort [(style :error-label) "Error:"] " no console to read password from"))
606607

607608
(print (ansi/compose prompt))
608609
(flush))
@@ -613,5 +614,5 @@
613614
"")]
614615
(when (and (string/blank? s)
615616
(not allow-blank?))
616-
(abort [:bold.red "Error:"] " password input may not be blank"))
617+
(abort [(style :error-label) "Error:"] " password input may not be blank"))
617618
s)))

src/net/lewisship/cli_tools/colors.clj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
(ns net.lewisship.cli-tools.colors
2-
{:command-ns 'net.lewisship.cli-tools.builtins}
32
(:require [clj-commons.ansi :refer [pout]]
43
[clojure.string :as string]
54
[net.lewisship.cli-tools :refer [defcommand]]))

src/net/lewisship/cli_tools/completions.clj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
(ns net.lewisship.cli-tools.completions
22
"Support for generating zsh command completion scripts for a command."
3-
{:command-ns 'net.lewisship.cli-tools.builtins}
43
(:require [babashka.fs :as fs]
54
[clojure.java.io :as io]
65
[net.lewisship.cli-tools :refer [defcommand abort command-path]]

src/net/lewisship/cli_tools/impl.clj

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"Private namespace for implementation details for new.lewisship.cli-tools, subject to change."
33
(:require [clojure.string :as string]
44
[clj-commons.ansi :refer [compose pout perr]]
5+
[net.lewisship.cli-tools.styles :refer [style]]
56
[clojure.tools.cli :as cli]
67
[clj-commons.humanize :as h]
78
[clj-commons.humanize.inflect :as inflect]
@@ -186,12 +187,13 @@
186187
"Usage: "
187188
;; A stand-alone tool doesn't have a tool-name (*options* will be nil)
188189
(when tool-name
189-
[:bold.green tool-name " "])
190+
[(style :tool-name) tool-name " "])
190191
;; A stand-alone tool will use its command-name, a command within
191192
;; a multi-command tool will have a command-path.
192-
[:bold.green (if command-path
193-
(string/join " " command-path)
194-
command-name)]
193+
[(style :command-path)
194+
(if command-path
195+
(string/join " " command-path)
196+
command-name)]
195197
" [OPTIONS]"
196198
(map list (repeat " ") arg-strs))
197199
(when command-doc
@@ -210,7 +212,7 @@
210212
lines (for [{:keys [label doc]} positional-specs]
211213
(list
212214
[{:width max-label-width}
213-
[:bold label]]
215+
[(style :option-label) label]]
214216
": "
215217
doc))]
216218
(pout "\nArguments:")
@@ -220,7 +222,7 @@
220222
[errors]
221223
(let [{:keys [tool-name]} *tool-options*]
222224
(perr
223-
[:red
225+
[(style :parse-error)
224226
(inflect/pluralize-noun (count errors) "Error")
225227
(when tool-name
226228
(list
@@ -266,9 +268,9 @@
266268
(when default-fn "<computed>")
267269
"")
268270
"")]
269-
{:opt-label [:bold opt-label]
271+
{:opt-label [(style :option-label) opt-label]
270272
:opt-width (.length opt-label)
271-
:default [:italic default-desc]
273+
:default [(style :option-default) default-desc]
272274
:default-width (.length default-desc)
273275
:opt-desc desc}))
274276

@@ -707,7 +709,9 @@
707709
(collect-subs command-root *result)
708710
(-> *result deref persistent!)))
709711

710-
(def ^:private missing-doc [:yellow.italic "(missing documentation)"])
712+
(defn- missing-doc
713+
[]
714+
[(style :missing-doc) "(missing documentation)"])
711715

712716
(defn extract-command-title
713717
[command-map]
@@ -733,7 +737,7 @@
733737
(list
734738
(h/numberword group-count) " "
735739
(inflect/pluralize-noun group-count "sub-group")))]))
736-
missing-doc))
740+
(missing-doc)))
737741

738742
(defn- print-commands
739743
[command-name-width container-map commands-map recurse?]
@@ -747,10 +751,10 @@
747751
(reduce max 0)))]
748752
(when container-map
749753
(pout (when recurse? "\n")
750-
[:bold (string/join " " (:command-path container-map))]
754+
[(style :command) (string/join " " (:command-path container-map))]
751755
" - "
752756
(or (some-> container-map :group-doc cleanup-docstring)
753-
missing-doc)))
757+
(missing-doc))))
754758

755759
(when (seq sorted-commands)
756760
(pout "\nCommands:"))
@@ -759,9 +763,9 @@
759763
(doseq [{:keys [fn command] :as command-map} sorted-commands]
760764
(pout
761765
" "
762-
[{:width command-name-width'} [:bold.green command]]
766+
[{:width command-name-width'} [(style :command-path) command]]
763767
": "
764-
[(when-not fn :italic)
768+
[(when-not fn (style :subgroup-label))
765769
(extract-command-title command-map)]))
766770

767771
;; Recurse and print sub-groups
@@ -781,7 +785,7 @@
781785
matching-commands (->> (filter #(command-match? % search-term') all-commands)
782786
(map #(update % :command-path join-command-path)))]
783787
(if-not (seq matching-commands)
784-
(pout "No commands match " [:italic search-term'])
788+
(pout "No commands match " [(style :search-term) search-term'])
785789
(let [command-width (->> matching-commands
786790
(map :command-path)
787791
(map count)
@@ -792,10 +796,10 @@
792796
(if (= n 1)
793797
" command matches "
794798
" commands match ")
795-
[:italic search-term']
799+
[(style :search-term) search-term']
796800
":" "\n")
797801
(doseq [{:keys [command-path] :as command} (sort-by :command-path matching-commands)]
798-
(pout [{:font :bold.green
802+
(pout [{:font (style :command-path)
799803
:width command-width}
800804
command-path]
801805
": "
@@ -805,7 +809,7 @@
805809
[level]
806810
(let [{tool-doc :doc
807811
:keys [tool-name command-root]} *tool-options*]
808-
(pout "Usage: " [:bold.green tool-name] " [OPTIONS] COMMAND ...")
812+
(pout "Usage: " [(style :tool-name) tool-name] " [OPTIONS] COMMAND ...")
809813
(when tool-doc
810814
(pout "\n"
811815
(cleanup-docstring tool-doc)))
@@ -841,21 +845,22 @@
841845
(sort (filter (to-matcher s) values')))))
842846

843847
(def ^:private help
844-
(list
845-
[:bold.green "--help"] " (or " [:bold.green "-h"] ") to list commands"))
848+
(let [option-style (style :option-name)]
849+
(list
850+
[option-style "--help"] " (or " [option-style "-h"] ") to list commands")))
846851

847852
(defn- use-help-message
848853
[tool-name]
849-
(list ", use " [:bold.green tool-name] " " help))
854+
(list ", use " [(style :tool-name) tool-name] " " help))
850855

851856
(defn- no-command
852857
[tool-name]
853-
(abort [:bold.green tool-name] ": no command provided" (use-help-message tool-name)))
858+
(abort [(style :tool-name) tool-name] ": no command provided" (use-help-message tool-name)))
854859

855860
(defn- incomplete
856861
[tool-name command-path matchable-terms]
857862
(abort
858-
[:bold.green tool-name ": "
863+
[(style :tool-name) tool-name ": "
859864
(string/join " " command-path)]
860865
" is incomplete; "
861866
(compose-list matchable-terms)
@@ -877,10 +882,11 @@
877882
" "
878883
help)]
879884
(abort
880-
[:bold [:green tool-name] ": "
881-
[:green (string/join " " command-path)]
885+
[(style :no-command-match)
886+
[(style :tool-name) tool-name] ": "
887+
[(style :command-path) (string/join " " command-path)]
882888
(when (seq command-path) " ")
883-
[:red term]]
889+
[(style :unknown-term) term]]
884890
" "
885891
body
886892
help-suffix)))

src/net/lewisship/cli_tools/job_status_demo.clj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
[net.lewisship.cli-tools.job-status :as j]))
55

66
(defn sleep
7-
([ms] (Thread/sleep ms))
8-
([job-id ms]
7+
([^long ms] (Thread/sleep ms))
8+
([job-id ^long ms]
99
(Thread/sleep ms)
1010
job-id))
1111

@@ -68,4 +68,4 @@
6868
(j/summary "Please fasten your Bat-seatbelts")
6969
done)
7070

71-
(Thread/sleep 3000))
71+
(sleep 3000))
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
(ns net.lewisship.cli-tools.styles
2+
"Defines styles (ANSI fonts) for output from cli-tools."
3+
{:added "0.16.0"})
4+
5+
(def default-styles
6+
{:invalid-input :yellow
7+
:possible-completion :italic
8+
:default-value :bold
9+
:error-label :bold.red
10+
:tool-name :bold.green
11+
:command-path :bold.green
12+
:option-label :bold
13+
:option-default :italic
14+
:parse-error :red
15+
:missing-doc :yellow.italic
16+
:command :bold
17+
:subgroup-label :italic
18+
:search-term :italic
19+
:option-name :bold.green
20+
:no-command-match :bold
21+
:unknown-term :red})
22+
23+
(def ^:dynamic *default-styles* default-styles)
24+
25+
(defn style
26+
"Retrieves a style; searches in *default-styles* first and, if not found, then in the default-styles."
27+
[k]
28+
(or (get *default-styles* k)
29+
(get default-styles k)))

0 commit comments

Comments
 (0)