Miguel de Benito Delgado 67d99366d9 ♻️ Command specific parsing
AI-assisted-by: gpt-6.1-sol, deepseek-flash
2026-11-02 18:46:05 +01:00

112 lines
5.0 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns benches.render-wasm.node.cli
"Command-specific argument resolution and help for renderer benchmarks."
(:require
[benches.render-wasm.feature-plan :as feature-plan]
[benches.render-wasm.node.cli.console :as console]
[benches.render-wasm.node.cli.help :as help]
[benches.render-wasm.node.cli.registry :as registry]
[benches.render-wasm.node.cli.shared :as shared]
[benches.render-wasm.node.request :as request]
[clojure.string :as str]
[clojure.tools.cli :as tools]))
(def ^:private commands
(into {} registry/commands))
(defn- get-definition
[topic]
(if (nil? topic)
{:usage help/usage :description help/description :options []}
(get commands topic)))
(defn- get-declarations
[definition]
(into help/cli-options (concat console/cli-options (:options definition))))
(defn- argument-error
[topic message cause]
(let [usage (or (:usage (get-definition topic)) help/usage)]
(ex-info (str message "\nUsage: " usage)
(assoc (ex-data cause)
:type ::invalid-args :cause-type (:type (ex-data cause))
:command topic :usage usage)
cause)))
(defn parse-resolve
"Returns a complete request for command-first argv, or throws an argument error.
A leading pnpm forwarding delimiter is accepted."
[argv]
(let [argv (if (= "--" (first argv)) (rest argv) argv)
first-arg (first argv)
topic (when (and first-arg (not (str/starts-with? first-arg "-"))) first-arg)
args (if topic (rest argv) argv)]
(when (and topic (not (contains? commands topic)))
(throw (argument-error topic (str "unknown command: " topic) nil)))
(let [definition (get-definition topic)]
(try
(let [{:keys [options arguments errors]}
(tools/parse-opts args (get-declarations definition) :no-defaults true)
_ (when (seq errors)
(throw (ex-info (str/join "\n" errors) {:type ::shared/invalid-args})))
console (console/resolve-options options)
partial (if (:help options)
(do
(when (and (nil? topic) (seq arguments))
(throw (ex-info "a command must precede its options"
{:type ::shared/invalid-args})))
{:command "help" :console console :options {:topic topic}})
(do
(when-not topic
(throw (ex-info "a command is required" {:type ::shared/invalid-args})))
{:command topic :console console
:options ((:resolve definition)
(dissoc options :help :quiet :verbose :color) arguments)}))
resolved (request/check (request/apply-defaults partial))]
(if (and (not= "help" (:command resolved)) (:check definition))
(update resolved :options (:check definition))
resolved))
(catch :default cause
(if (contains? #{::shared/invalid-args ::request/invalid-request
::feature-plan/too-many-features ::feature-plan/confirmation-required}
(:type (ex-data cause)))
(throw (argument-error topic (ex-message cause) cause))
(throw cause)))))))
(defn- get-defaults
[topic]
(let [definition (get-definition topic)
partial {:command (or topic "help") :options (or (:help-options definition) {})}
resolved (request/apply-defaults partial)
options ((or (:help-defaults definition) identity) (:options resolved))]
(merge (:console resolved) options)))
(defn get-help
"Returns root help for nil or help for a known command name."
[topic]
(let [definition (get-definition topic)
defaults (get-defaults topic)
summary (:summary
(tools/parse-opts
[] (get-declarations definition)
:no-defaults true
:summary-fn
(fn [specs]
(tools/summarize
(mapv (fn [{:keys [id] :as spec}]
(if (contains? defaults id)
(assoc spec :default (get defaults id)
:default-desc (pr-str (get defaults id)))
spec))
specs)))))]
(str "Usage: " (:usage definition) "\n\n" (:description definition) "\n\n"
(when (nil? topic)
(str (str/join "\n" (map (fn [[_ definition]] (str " " (:usage definition))) registry/commands))
"\n\n"))
"Options:\n" summary)))