|
4 | 4 | [clj-commons.ansi :as ansi] |
5 | 5 | [clojure.string :as string] |
6 | 6 | [net.lewisship.cli-tools.impl :as impl :refer [cond-let]] |
| 7 | + [clojure.tools.cli :as cli] |
7 | 8 | [net.lewisship.cli-tools.cache :as cache])) |
8 | 9 |
|
9 | 10 | (defn exit |
|
162 | 163 | ~validations) |
163 | 164 | ~@body)))))) |
164 | 165 |
|
165 | | -;; TODO: Expandable expansion and caching |
166 | 166 |
|
167 | | -(defn dispatch* |
168 | | - "Invoked by [[dispatch]] after namespace and command resolution." |
169 | | - [expanded-options] |
170 | | - (impl/dispatch expanded-options)) |
171 | | - |
172 | | -(defn expand-dispatch-options |
173 | | - "Called by [[dispatch]] to expand the options before calling [[dispatch*]]. |
174 | | - Some applications may call this instead of `dispatch`, modify the results, and then |
175 | | - invoke `dispatch*`." |
| 167 | +(def ^{:added "0.16.0"} |
| 168 | + default-tool-options |
| 169 | + "Default tool command line options." |
| 170 | + [["-C" "--color" "Enable ANSI color output"] |
| 171 | + ["-N" "--no-color" "Disable ANSI color output"] |
| 172 | + ["-h" "--help" "This tool summary"]]) |
| 173 | + |
| 174 | +(defn- expand-tool-options |
| 175 | + "Expanded dispatch options into tool options, leveraging a cache." |
176 | 176 | [options] |
177 | | - (let [{:keys [cache-dir arguments]} options |
178 | | - options' (select-keys options [:tool-name |
179 | | - :doc |
180 | | - :namespaces |
181 | | - :groups]) |
182 | | - result (if-not cache-dir |
183 | | - (impl/expand-dispatch-options options') |
184 | | - (let [cache-dir' (fs/expand-home cache-dir) |
185 | | - digest (cache/classpath-digest options') |
186 | | - cached (cache/read-from-cache cache-dir' digest)] |
187 | | - (if cached |
188 | | - cached |
189 | | - (let [full (impl/expand-dispatch-options options')] |
190 | | - (cache/write-to-cache cache-dir' digest full) |
191 | | - full))))] |
192 | | - (assoc result :arguments (or arguments *command-line-args*)))) |
193 | | - |
194 | | -(def ^:private default-options |
| 177 | + (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')] |
| 191 | + (when cache-dir' |
| 192 | + (cache/write-to-cache cache-dir' digest expanded)) |
| 193 | + expanded))] |
| 194 | + (merge result |
| 195 | + (select-keys options [:tool-name :doc :arguments :tool-summary])))) |
| 196 | + |
| 197 | +(defn dispatch* |
| 198 | + "Called from a tool handler to process remaining command line arguments. |
| 199 | +
|
| 200 | + - dispatch-options - modified dispatch options |
| 201 | + - color-flag - if non-nil, enables or disables ANSI colors before dispatching |
| 202 | + - help - if true, then change the arguments to \"help\", to print tool help |
| 203 | +
|
| 204 | + In the dispatch options map, the tool handler should have set the following: |
| 205 | +
|
| 206 | + - :arguments -- seq of remaining arguments after processing tool-level options |
| 207 | + - :tool-summary -- summary of tool options (used when printing tool help)." |
| 208 | + [dispatch-options color-flag help?] |
| 209 | + (cond |
| 210 | + (some? color-flag) |
| 211 | + (binding [ansi/*color-enabled* color-flag] |
| 212 | + (dispatch* dispatch-options nil help?)) |
| 213 | + |
| 214 | + help? |
| 215 | + (recur (assoc dispatch-options :arguments ["help"]) nil false) |
| 216 | + |
| 217 | + :else |
| 218 | + (-> dispatch-options expand-tool-options impl/dispatch))) |
| 219 | + |
| 220 | +(defn summarize-specs |
| 221 | + "Converts a tools.cli command specification to a description of the options; this is an enhanced version of |
| 222 | + clojure.tools.cli/summarize that makes use of indentation and ANSI colors. |
| 223 | +
|
| 224 | + Returns a delay (to ensure that ANSI color enabled/disabled options are enforced)." |
| 225 | + {:added "0.16.0"} |
| 226 | + [specs] |
| 227 | + ;; summarize-specs is called before we parse the command line options (-C, -N) that may enable/disable |
| 228 | + ;; ANSI colors, so a delay is used to prevent premature evaluation. |
| 229 | + (delay (impl/summarize-specs specs))) |
| 230 | + |
| 231 | +(defn default-tool-handler |
| 232 | + "Default tool handler, passed the tool options. The [[default-tool-options]] support enabling or disabling |
| 233 | + ANSI fonts, and requesting top-level help. |
| 234 | +
|
| 235 | + This is the default for the :handler key of dispatch options. |
| 236 | +
|
| 237 | + This function is passed the dispatch options, parses the default tool options, and delegates the rest to [[dispatch*]]." |
| 238 | + {:added "0.16.0"} |
| 239 | + [dispatch-options] |
| 240 | + (let [{:keys [options arguments summary errors] |
| 241 | + :as result} (cli/parse-opts (:arguments dispatch-options) |
| 242 | + default-tool-options |
| 243 | + :in-order true |
| 244 | + :summary-fn summarize-specs) |
| 245 | + {:keys [color no-color help]} options |
| 246 | + color-flag (cond color true |
| 247 | + no-color false)] |
| 248 | + (when errors |
| 249 | + (throw (ex-info "Tool parse sanity check" result))) |
| 250 | + |
| 251 | + (dispatch* (-> dispatch-options |
| 252 | + (assoc :arguments arguments |
| 253 | + :tool-summary summary)) |
| 254 | + color-flag help))) |
| 255 | + |
| 256 | +(def ^:private default-dispatch-options |
195 | 257 | {:cache-dir (or (System/getenv "CLI_TOOLS_CACHE_DIR") |
196 | | - "~/.cli-tools-cache")}) |
| 258 | + "~/.cli-tools-cache") |
| 259 | + :handler default-tool-handler}) |
197 | 260 |
|
198 | 261 | (defn dispatch |
199 | 262 | "Locates commands in namespaces, finds the current command |
200 | 263 | (as identified by the first command line argument) and processes CLI options and arguments. |
201 | 264 |
|
202 | | - options: |
| 265 | + dispatch-options: |
203 | 266 | |
204 | 267 | - :tool-name (optional, string) - used in command summary and errors |
205 | 268 | - :doc (optional, string) - used in help summary |
206 | 269 | - :arguments - command line arguments to parse (defaults to `*command-line-args*`) |
207 | 270 | - :namespaces - seq of symbols identifying namespaces to search for root-level commands |
208 | 271 | - :groups - map of group command (a string) to a group map |
| 272 | + - :handler - function to handle tool-level options, defaults to [[default-tool-handler]] |
| 273 | + - :cache-dir (optional, string) - directory to cache data in, or nil to disable cache |
209 | 274 |
|
210 | 275 | The :tool-name option is only semi-optional; in a Babashka script, it will default |
211 | 276 | from the `babashka.file` system property, if any. An exception is thrown if :tool-name |
212 | 277 | is not provided and can't be defaulted. |
213 | 278 |
|
214 | 279 | A group map defines a set of commands grouped under a common name. Its structure: |
215 | 280 |
|
216 | | - - :doc - string, a short string identifying the purpose of the group |
217 | | - - :namespaces - seq of symbols identifying namespaces of commands in the group |
218 | | - - :groups - recusive map of groups nested within the group |
| 281 | + - :doc (optional, string) - a short string identifying the purpose of the group |
| 282 | + - :namespaces (seq of symbols, required) - identifies namespaces providing commands in the group |
| 283 | + - :groups (optional, map) - recusive map of groups nested within the group |
219 | 284 |
|
220 | 285 | dispatch will always add the `net.lewiship.cli-tools.builtins` namespace to the root |
221 | 286 | namespace list; this ensures the built-in `help` command is available. |
|
226 | 291 |
|
227 | 292 | Caching is enabled by default; this means that a scan of all namespaces is only required on the first |
228 | 293 | execution; subsequently, only the single namespace implementing the selected command will need to |
229 | | - be loaded. |
| 294 | + be loaded. :cache-dir defaults to the value of the CLI_TOOLS_CACHE_DIR environment variable, or |
| 295 | + to the default value `~/.cli-tools-cache`. If set to nil, then caching is disabled. |
230 | 296 |
|
231 | 297 | Returns nil." |
232 | | - [options] |
233 | | - (-> (merge default-options options) |
234 | | - expand-dispatch-options |
235 | | - dispatch*)) |
| 298 | + [dispatch-options] |
| 299 | + (let [options' (merge {:arguments *command-line-args*} |
| 300 | + default-dispatch-options |
| 301 | + dispatch-options) |
| 302 | + {:keys [handler]} options'] |
| 303 | + (handler (dissoc options' :handler)))) |
236 | 304 |
|
237 | 305 | (defn select-option |
238 | 306 | "Builds a standard option spec for selecting from a list of possible values. |
|
0 commit comments