Coverage report: /home/ellis/comp/core/lib/cli/clap/macs.lisp
Kind | Covered | All | % |
expression | 51 | 268 | 19.0 |
branch | 0 | 44 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; cli/clap/macs.lisp --- Clap Macros
6
(in-package :cli/clap/macs)
8
(defmacro argp (arg &optional (args (args)))
9
"Test for presence of ARG in ARGS. Return the tail of
10
ARGS starting from the position of ARG."
11
`(member ,arg ,args :test 'equal))
13
(defmacro make-shorty (name)
14
"Return the first char of symbol or string NAME."
15
`(character (aref (if (stringp ,name) ,name (symbol-name ,name)) 0)))
17
(defmacro with-cli-handlers (&body body)
18
"A wrapper which handles common cli errors that may occur during
22
(sb-ext:disable-debugger)
23
(sb-ext:enable-debugger))
26
(handler-case (progn ,@body)
27
(sb-sys:interactive-interrupt (c)
29
(sb-ext:exit :code 130)
33
(sb-ext:exit :code 1)))
37
"Skip to toplevel READ/EVAL/PRINT loop."
39
(log:debug! "CONTINUEing from pre-REPL RESTART-CASE")
42
:report "Exit SBCL (calling #'EXIT, killing the process)."
43
;; :test (lambda (c) (declare (ignore c)) t)
44
(log:debug! "falling through to EXIT from pre-REPL RESTART-CASE~&")
46
(sb-impl::flush-standard-output-streams)
49
;; reset terminal state
52
(define-constant +cli-lambda-list-keywords+ '(&rest &optional &opt &key) :test 'equal)
54
;; TODO 2025-01-05: env? for shell env
55
(defun parse-cli-lambda-list (ll)
56
"Parse a specialized CLI lambda-list, returning as multiple values:
63
(let ((state :required)
70
(simple-program-error "Misplaced ~S in ordinary lambda-list:~% ~S"
73
(unless (and (or (symbolp l)
74
(and (consp l) (= 2 (length l)) (symbolp (first l))))
76
(simple-program-error "Invalid ~A ~S in ordinary lambda-list:~% ~S"
78
(%check-spec (spec what)
79
(destructuring-bind (init suppliedp) spec
80
(declare (ignore init))
81
(%check-var suppliedp what))))
85
(if (eq state :required)
89
(if (member state '(:required &optional))
93
(if (member state '(:required &optional :after-rest &key))
97
(if (member state '(:required &optional :after-rest &opt))
101
(when (member l '#.(set-difference lambda-list-keywords
102
'(&optional &rest &key &allow-other-keys &aux &opt)))
103
(simple-program-error
104
"Bad lambda-list keyword ~S in ordinary lambda-list:~% ~S"
108
(%check-var l "required parameter")
112
(destructuring-bind (name &rest tail) l
113
(%check-var name "optional parameter")
115
(%check-spec tail "optional-supplied-p parameter"))))
116
(%check-var l "optional parameter"))
117
(push (ensure-list l) optional))
120
(destructuring-bind (name &rest tail) l
121
(%check-var name "opt parameter")
122
(when (cdr tail) (%check-spec tail "opt parameter")))
123
(%check-var l "opt parameter"))
124
(push (ensure-list l) opts))
126
(%check-var l "rest parameter")
131
(destructuring-bind (var-or-kv &rest tail) l
132
(if (consp var-or-kv)
133
(destructuring-bind (keyword var) var-or-kv
134
(unless (symbolp keyword)
135
(simple-program-error "Invalid key name ~S in ordinary ~
138
(%check-var var "key parameter"))
139
(%check-var var-or-kv "key parameter"))
141
(%check-spec tail "key parameter"))
142
(setf l (cons var-or-kv tail)))
143
(%check-var l "key parameter"))
144
(push (ensure-list l) key-opts))
145
(t (simple-program-error "invalid cli lambda-list:~% ~S" ll)))))))
146
(values (nreverse required)
150
(nreverse key-opts))))
152
#+nil (parse-cli-lambda-list '(arg1 arg2 &optional (arg3 "foo") &rest rest &opt opt1 opt2 &key key1 key2))
154
;; DEFCMD always returns a function of two argument ARGS and OPTS - the
155
;; cli-lambda-list is applied to the BODY instead of closing over the
157
(defmacro defcmd (name cli-lambda-list &body body)
158
"Bind NAME to a functions which accepts a CLI-LAMBDA-LIST containing a
159
specialized lambda-list with the following keywords:
161
- &OPTIONAL is an optional positional argument in ARGS
162
- &REST specifies the remainder of the ARGS passed at the CLI
163
- &OPT specifies a set of cli options
164
- &KEY specifies a set of cli keywords
166
CLI-LAMBDA-LIST is a list which automatically selects and binds the values of
167
parsed CLI-OPTs to a name via SYMBOL-MACROLET. The forms accepted are the same
168
as the SLOTS args to WITH-SLOTS - the CAR is used as the name of the local
169
symbol binding and the CDR is the actual name of the CLI-OPT.
171
The following special variables are bound for the duration of BODY:
173
- *ARGC* : the count of arguments passed to this command
174
- *ARGS* : the actual list of args
175
- *OPTC* : the count of options passed to this command
176
- *OPTS* : the actual list of options"
177
(multiple-value-bind (required optional rest opts keys) (parse-cli-lambda-list cli-lambda-list)
178
(multiple-value-bind (body decl doc-string) (parse-body body :documentation t)
179
`(defun ,name (args opts)
180
,(let ((%d '(ignorable args opts)))
182
(append decl (list %d))
185
(let ((*argc* (length args))
186
(*optc* (length opts))
193
(cons symbol (cons symbol null))))
194
(error "Malformed CLI-OPT binding: ~s, should either a symbol or (variable-name opt-name)" x))
195
(destructuring-bind (name &optional (opt-name name)) (ensure-list x)
197
(when-let ((val (find ,(string-downcase opt-name) *opts*
199
:key 'cli/clap/obj:cli-opt-name)))
200
(cli-opt-val val)))))
204
;; DEFOPTS are much simpler - they always take a single optional argument and
205
;; have no lambda-list that needs to be applied.
206
(defmacro defopt (name &body body)
207
(multiple-value-bind (body decl doc-string) (parse-body body :documentation t)
208
`(defun ,name (&optional arg)
209
,(let ((%d '(ignorable arg)))
211
(append decl (list %d))
213
,@(when doc-string (list doc-string))
217
(defmacro defopts (&body body)
219
`(progn ,@(mapcar (lambda (x) `((defopt ,@x))) body))))
222
;; (defmacro gen-cli-thunk (pvars &rest thunk)
223
;; "Generate and return a function based on THUNK suitable for the :thunk
224
;; slot of cli objects with pandoric bindings PVARS.")
226
(defmacro make-opt-parser (kind-spec &body body)
227
"Return a KIND-opt-parser function based on KIND-SPEC which is either a
228
symbol from *CLI-OPT-KINDS* or a list, and optional BODY which
229
is a list of handlers for the opt-val."
230
(let* ((kind (if (consp kind-spec) (car kind-spec) kind-spec))
231
(super (when (consp kind-spec) (cadr kind-spec)))
232
(fn-name (symbolicate 'parse- kind '-opt)))
234
(let ((fn1 (unless (null super) (symbolicate "PARSE-" super "-OPT"))))
235
`(defun ,fn-name (&optional arg)
236
"Parse the cli-opt-val *ARG*."
237
(declare (ignorable arg))
239
`((setf *arg* (funcall #',fn1 arg)))