--- a/src/main.lisp Tue May 21 22:05:24 2019 -0400
+++ b/src/main.lisp Tue May 21 22:51:10 2019 -0400
@@ -55,9 +55,9 @@
(defun argv ()
"Return a list of the program name and command line arguments.
- This is not implemented for every Common Lisp implementation. You can always
- pass your own values to `parse-options` and `print-help` if it's not
- implemented for your particular Lisp.
+ This is not implemented for every Common Lisp implementation. You can pass
+ your own values to `parse-options` and `print-help` if it's not implemented
+ for your particular Lisp.
"
#+sbcl sb-ext:*posix-argv*
@@ -67,6 +67,13 @@
#-(or sbcl ccl) (error "ARGV is not supported on this implementation."))
(defun exit (&optional (code 0))
+ "Exit the program with status `code`.
+
+ This is not implemented for every Common Lisp implementation. You can write
+ your own version of it and pass it to `print-help-and-exit` and
+ `print-error-and-exit` if it's not implemented for your particular Lisp.
+
+ "
#+sbcl (sb-ext:exit :code code)
#+ccl (ccl:quit code)
#-(or sbcl ccl) (error "EXIT is not supported on this implementation."))
@@ -121,10 +128,38 @@
manual
parameter
reduce
+ ;; can't just default to nil because multiple options might
+ ;; have the same result key, and only one can provide init
+ (initial-value nil initial-value?)
(result-key name)
- (initial-value nil initial-value?)
(key #'identity)
(finally #'identity))
+ "Create and return an option, suitable for use in an interface.
+
+ This function takes a number of arguments, some required, that define how the
+ option interacts with the user.
+
+ * `name` (**required**): a symbol naming the option.
+ * `help` (**required**): a short string describing what the option does.
+ * `result-key` (optional): a symbol to use as the key for this option in the hash table of results.
+ * `long` (optional): a string for the long form of the option (e.g. `--foo`).
+ * `short` (optional): a character for the short form of the option (e.g. `-f`). At least one of `short` and `long` must be given.
+ * `manual` (optional): a string to use in place of `help` when rendering a man page.
+ * `parameter` (optional): a string. If given, it will turn this option into a parameter-taking option (e.g. `--foo=bar`) and will be used as a placeholder
+ in the help text.
+ * `reduce` (**required**): a function designator that will be called every time the option is specified by the user.
+ * `initial-value` (optional): a value to use as the initial value of the option.
+ * `key` (optional): a function designator, only allowed for parameter-taking options, to be called on the values given by the user before they are passed along to the reducing function. It will not be called on the initial value.
+ * `finally` (optional): a function designator to be called on the final result after all parsing is complete.
+
+ The manner in which the reducer is called depends on whether the option takes a parameter:
+
+ * For options that don't take parameters, it will be called with the old value.
+ * For options that take parameters, it will be called with the old value and the value given by the user.
+
+ See the full documentation for more information.
+
+ "
(when (and (null long) (null short))
(error "Option ~A requires at least one of :long/:short." name))
(when (null reduce)
@@ -143,18 +178,18 @@
manual (or null string)
parameter (or null string))
(apply #'make-instance 'option
- :name name
- :result-key result-key
- :help help
- :manual manual
- :long long
- :short short
- :parameter parameter
- :reduce reduce
- :key key
- :finally finally
- (when initial-value?
- (list :initial-value initial-value))))
+ :name name
+ :result-key result-key
+ :help help
+ :manual manual
+ :long long
+ :short short
+ :parameter parameter
+ :reduce reduce
+ :key key
+ :finally finally
+ (when initial-value?
+ (list :initial-value initial-value))))
(defun optionp (object)
(typep object 'option))
@@ -168,10 +203,28 @@
(format stream "~A (~D options)" (name g) (length (options g)))))
(defun make-group (name &key title help manual options)
- (check-types title (or null string)
+ "Create and return an option group, suitable for use in an interface.
+
+ This function takes a number of arguments that define how the group is
+ presented to the user:
+
+ * `name` (**required**): a symbol naming the group.
+ * `title` (optional): a title for the group for use in the help text.
+ * `help` (optional): a short summary of this group of options for use in the help text.
+ * `manual` (optional): used in place of `help` when rendering a man page.
+ * `options` (**required**): the options to include in the group.
+
+ See the full documentation for more information.
+
+ "
+ (check-types name symbol
+ title (or null string)
help (or null string)
manual (or null string)
options list)
+ (assert (every #'optionp options) (options)
+ "The :options argument to ~S was not a list of options. Got: ~S"
+ 'make-group options)
(make-instance 'group
:name name
:title title
@@ -211,6 +264,22 @@
(length (groups i)))))
(defun make-interface (&key name summary usage help manual examples contents)
+ "Create and return a command line interface.
+
+ This function takes a number of arguments that define how the interface is
+ presented to the user:
+
+ * `name` (**required**): a symbol naming the interface.
+ * `summary` (**required**): a string of a concise, one-line summary of what the program does.
+ * `usage` (**required**): a string of a UNIX-style usage summary, e.g. \"[OPTIONS] PATTERN [FILE...]\".
+ * `help` (**required**): a string of a longer description of the program.
+ * `manual` (optional): a string to use in place of `help` when rendering a man page.
+ * `examples` (optional): an alist of `(prose . command)` conses to render as a list of examples.
+ * `contents` (optional): a list of options and groups. Ungrouped options will be collected into a single top-level group.
+
+ See the full documentation for more information.
+
+ "
(check-types name string
summary string
usage string
@@ -246,6 +315,7 @@
(setf (gethash long (long-options interface)) option)))))
(dolist (g groups)
(map nil #'add-option (options g))))
+ ;; TODO: check for multiple conflicting initial-values
interface))
@@ -532,6 +602,7 @@
(width 80)
(option-width 20)
(include-examples t)
+ (exit-function #'exit)
(exit-code 0))
"Print a pretty help document for `interface` to `stream` and exit.
@@ -541,17 +612,19 @@
(when (gethash 'help options)
(print-help-and-exit *ui*))
(run arguments options))
+
"
(print-help interface
- :stream stream
- :program-name program-name
- :width width
- :option-width option-width
- :include-examples include-examples)
- (exit exit-code))
+ :stream stream
+ :program-name program-name
+ :width width
+ :option-width option-width
+ :include-examples include-examples)
+ (funcall exit-function exit-code))
(defun print-error-and-exit (error &key
(stream *error-output*)
+ (exit-function #'exit)
(exit-code 1)
(prefix "error: "))
"Print `prefix` and `error` to `stream` and exit.
@@ -566,7 +639,7 @@
"
(format stream "~A~A~%" (or prefix "") error)
- (exit exit-code))
+ (funcall exit-function exit-code))
;;;; Man ----------------------------------------------------------------------
@@ -610,6 +683,16 @@
(defun print-manual (interface &key
(stream *standard-output*)
(manual-section 1))
+ "Print a troff-formatted man page for `interface` to `stream`.
+
+ Example:
+
+ (with-open-file (manual \"man/man1/foo.1\"
+ :direction :output
+ :if-exists :supersede)
+ (print-manual *ui* manual))
+
+ "
(check-type manual-section (integer 1))
(labels
((f (&rest args)
--- a/src/package.lisp Tue May 21 22:05:24 2019 -0400
+++ b/src/package.lisp Tue May 21 22:51:10 2019 -0400
@@ -24,7 +24,6 @@
:supply-new-value
:flip
- :oldest
:collect
:first
:last