--- a/docs/01-usage.markdown Thu Jan 30 19:27:27 2020 -0500
+++ b/docs/01-usage.markdown Sat Dec 12 18:40:35 2020 -0500
@@ -37,7 +37,8 @@
`make-interface` takes several required arguments:
* `:name` is the name of the program.
-* `:summary` is a concise one-line summary of what it does.
+* `:summary` is a concise one-line summary of what it does. By convention it
+ should not be a full sentence, but just a snippet of text.
* `:usage` is a UNIX-style the command line usage string.
* `:help` is a longer description of the program.
@@ -240,21 +241,21 @@
(defparameter *option-version*
(adopt:make-option 'version
:long "version"
- :help "display version information and exit"
+ :help "Display version and exit."
:reduce (constantly t)))
(defparameter *option-help*
(adopt:make-option 'help
:long "help"
:short #\h
- :help "display help information and exit"
+ :help "Display help and exit."
:reduce (constantly t)))
(defparameter *option-literal*
(adopt:make-option 'literal
:long "literal"
:short #\l
- :help "treat PATTERN as a literal string instead of a regular expression"
+ :help "Treat PATTERN as a literal string instead of a regular expression."
:reduce (constantly t)))
(defparameter *ui*
@@ -280,16 +281,21 @@
; Search the contents of …
;
; Options:
- ; --version display version information and exit
- ; -h, --help display help information and exit
- ; -l, --literal treat PATTERN as a literal string instead of a regular
- ; expression
+ ; --version Display version and exit.
+ ; -h, --help Display help and exit.
+ ; -l, --literal Treat PATTERN as a literal string instead of a regular
+ ; expression.
The first argument to `make-option` is the name of the option, which we'll see
put to use shortly. At least one of `:short` and `:long` is required, and
`:help` text must be specified. We'll talk more about `:reduce` in a little
while, but it too is required.
+When writing `:help` text I recommend using a full sentence, starting with
+a capital letter and ending with appropriate punctuation. If there's a default
+value or behavior for the option, mention it in the help text with something
+like `Search at most N lines (default 100).`.
+
I prefer to define each option as its own global variable to keep the call to
`make-interface` from getting too large and unwieldy, but feel free to do
something like this if you prefer to avoid cluttering your package:
@@ -341,7 +347,7 @@
:result-key 'pattern-is-literal
:long "literal"
:short #\l
- :help "treat PATTERN as a literal string instead of a regular expression"
+ :help "Treat PATTERN as a literal string instead of a regular expression."
:reduce (constantly t)))
;; …
@@ -484,7 +490,7 @@
(adopt:make-option 'help
:long "help"
:short #\h
- :help "display help information and exit"
+ :help "Display help and exit."
:initial-value nil
:reduce (lambda (current-value)
(declare (ignore current-value))
@@ -499,7 +505,7 @@
(adopt:make-option 'help
:long "help"
:short #\h
- :help "display help information and exit"
+ :help "Display help and exit."
:reduce (constantly t)))
### Boolean Options
@@ -512,7 +518,7 @@
(adopt:make-option 'paginate
:long "paginate"
:short #\p
- :help "turn pagination on"
+ :help "Turn pagination on."
:reduce (constantly t)))
(defparameter *option-no-paginate*
@@ -520,7 +526,7 @@
:result-key 'paginate
:long "no-paginate"
:short #\P
- :help "turn pagination off (the default)"
+ :help "Turn pagination off (the default)."
:reduce (constantly nil)))
The way we've written this, if the user gives multiple options the last-given
@@ -540,6 +546,44 @@
non-alias version of the command, and type out all the options they *do* want by
hand. This is annoying, so it's usually better to let the last one win.
+Making two separate options by hand can be tedious if you have a lot of boolean
+options, so Adopt provides a `make-boolean-options` function that will do some
+of the boilerplate for you:
+
+ :::lisp
+ (adopt:make-boolean-options 'paginate
+ :long "paginate"
+ :short #\p
+ :help "Turn pagination on."
+ :help-no "Turn pagination off (the default).")
+ ;; =>
+ #<ADOPT::OPTION PAGINATE p/paginate>
+ #<ADOPT::OPTION NO-PAGINATE P/no-paginate>
+
+`make-boolean-options` will try to guess at sensible values to reduce the
+boilerplate you need to type:
+
+* If `:long` is `"foo"` for the true option, the false option will be `"no-foo"`
+ unless overridden by `:long-no`.
+* If `:short` is `#\f` for the true option, the false option will be
+ `(char-upcase #\f)` unless overridden by `:short-no`.
+* The given option name (e.g. `foo`) will be used for the true option, and
+ a symbol with `no-` prepended (e.g. `no-foo`) will be used for the false
+ option unless overridden by `:name-no`.
+* `:initial-value` will be `nil` for the pair if not given.
+
+The two options are returned as separate `values`. Adopt also provides
+a `defparameters` convenience macro to create special variables for them more
+easily:
+
+ :::lisp
+ (defparameters (*option-paginate* *option-no-paginate*)
+ (adopt:make-boolean-options 'paginate
+ :long "paginate"
+ :short #\p
+ :help "Turn pagination on."
+ :help-no "Turn pagination off (the default)."))
+
### Counting Options
To define an option that counts how many times it's been given, like SSH's `-v`,
@@ -549,7 +593,7 @@
(defparameter *option-verbosity*
(adopt:make-option 'verbosity
:short #\v
- :help "output more verbose logs"
+ :help "Output more verbose logs."
:initial-value 0
:reduce #'1+))
@@ -564,7 +608,7 @@
:parameter "PATTERN"
:long "repository"
:short #\R
- :help "path to the repository (default .)"
+ :help "Path to the repository (default .)."
:initial-value "."
:reduce (lambda (prev new)
(declare (ignore prev))
@@ -583,7 +627,7 @@
(adopt:make-option 'repository
:long "repository"
:short #\R
- :help "path to the repository (default .)"
+ :help "Path to the repository (default .)."
:initial-value "."
:reduce #'adopt:last))
@@ -597,7 +641,7 @@
(adopt:make-option 'exclude
:long "exclude"
:parameter "PATTERN"
- :help "exclude PATTERN (may be given multiple times)"
+ :help "Exclude PATTERN (may be given multiple times)."
:initial-value nil
:reduce (lambda (patterns new)
(cons new patterns))))
@@ -612,7 +656,7 @@
(adopt:make-option 'exclude
:long "exclude"
:parameter "PATTERN"
- :help "exclude PATTERN (may be given multiple times)"
+ :help "Exclude PATTERN (may be given multiple times)."
:initial-value nil
:reduce (adopt:flip #'cons)))
@@ -628,7 +672,7 @@
(adopt:make-option 'exclude
:long "exclude"
:parameter "PATTERN"
- :help "exclude PATTERN (may be given multiple times)"
+ :help "Exclude PATTERN (may be given multiple times)."
:initial-value nil
:reduce (lambda (patterns new)
(append patterns (list new)))))
@@ -644,7 +688,7 @@
(adopt:make-option 'exclude
:long "exclude"
:parameter "PATTERN"
- :help "exclude PATTERN (may be given multiple times)"
+ :help "Exclude PATTERN (may be given multiple times)."
:reduce #'adopt:collect))
A more efficient (though slightly uglier) solution would be to use `nreverse` at
@@ -655,7 +699,7 @@
(adopt:make-option 'exclude
:long "exclude"
:parameter "PATTERN"
- :help "exclude PATTERN (may be given multiple times)"
+ :help "Exclude PATTERN (may be given multiple times)."
:reduce (adopt:flip #'cons)
:finally #'nreverse))
@@ -698,14 +742,14 @@
:::lisp
(defparameter *option-help*
(adopt:make-option 'help
- :help "display help and exit"
+ :help "Display help and exit."
:long "help"
:short #\h
:reduce (constantly t)))
(defparameter *option-literal*
(adopt:make-option 'literal
- :help "treat PATTERN as a literal string instead of a regex"
+ :help "Treat PATTERN as a literal string instead of a regex."
:long "literal"
:short #\l
:reduce (constantly t)))
@@ -713,14 +757,14 @@
(defparameter *option-no-literal*
(adopt:make-option 'no-literal
:result-key 'literal
- :help "treat PATTERN as a regex (the default)"
+ :help "Treat PATTERN as a regex (the default)."
:long "no-literal"
:short #\L
:reduce (constantly nil)))
(defparameter *option-case-sensitive*
(adopt:make-option 'case-sensitive
- :help "match case-sensitively (the default)"
+ :help "Match case-sensitively (the default)."
:long "case-sensitive"
:short #\c
:initial-value t
@@ -728,7 +772,7 @@
(defparameter *option-case-insensitive*
(adopt:make-option 'case-insensitive
- :help "ignore case when matching"
+ :help "Ignore case when matching."
:long "case-insensitive"
:short #\C
:result-key 'case-sensitive
@@ -736,13 +780,13 @@
(defparameter *option-color*
(adopt:make-option 'color
- :help "highlight matches with color"
+ :help "Highlight matches with color."
:long "color"
:reduce (constantly t)))
(defparameter *option-no-color*
(adopt:make-option 'no-color
- :help "don't highlight matches (the default)"
+ :help "Don't highlight matches (the default)."
:long "no-color"
:result-key 'color
:reduce (constantly nil)))
@@ -750,7 +794,7 @@
(defparameter *option-context*
(adopt:make-option 'context
:parameter "N"
- :help "show N lines of context (default 0)"
+ :help "Show N lines of context (default 0)."
:long "context"
:short #\U
:initial-value 0
@@ -852,7 +896,7 @@
:parameter "N"
:long "times"
:initial-value 1
- :help "say meow N times (default 1)"
+ :help "Say meow N times (default 1)."
:reduce #'adopt:last
:key #'parse-integer))))
@@ -923,7 +967,7 @@
(adopt:make-option 'exclude
:long "exclude"
:parameter "PATTERN"
- :help "exclude PATTERN"
+ :help "Exclude PATTERN."
:manual "Exclude lines that match PATTERN (a PERL-compatible regular expression) from the search results. Multiple PATTERNs can be specified by giving this option multiple times."
:reduce (adopt:flip #'cons)))
--- a/docs/02-reference.markdown Thu Jan 30 19:27:27 2020 -0500
+++ b/docs/02-reference.markdown Sat Dec 12 18:40:35 2020 -0500
@@ -44,6 +44,37 @@
Convenience macro for `(defparameter ,var (format nil ,string ,@args))`.
+### `DEFPARAMETERS` (macro)
+
+ (DEFPARAMETERS PARAMETERS VALUES-FORM)
+
+Convenience macro for `defparameter`ing multiple variables at once.
+
+ `parameters` must be a list of special variable names suitable for giving to
+ `defparameter`.
+
+ `values-form` must be an expression that returns as many values as parameters
+ in the parameter list. Each parameter will be set to the corresponding value.
+
+ This can be handy when using `make-boolean-options` to create two `option`s at
+ once and assign them to special variables.
+
+ Examples:
+
+ (defparameters (*a* *b*) (truncate 100 3))
+ (list *a* *b*)
+ ; =>
+ ; (33 1)
+
+ (defparameters (*option-foo* *option-no-foo*)
+ (make-boolean-options 'foo
+ :help "Foo the widgets during the run."
+ :help-no "Do not foo the widgets during the run (the default)."
+ :long "foo"
+ :short #f))
+
+
+
### `DISCARD-OPTION` (function)
(DISCARD-OPTION CONDITION)
@@ -110,6 +141,53 @@
+### `MAKE-BOOLEAN-OPTIONS` (function)
+
+ (MAKE-BOOLEAN-OPTIONS NAME &KEY
+ (NAME-NO (INTERN (CONCATENATE 'STRING (STRING 'NO-) (STRING NAME)))) LONG
+ (LONG-NO (WHEN LONG (FORMAT NIL no-~A LONG))) SHORT
+ (SHORT-NO (WHEN SHORT (CHAR-UPCASE SHORT))) (RESULT-KEY NAME) HELP
+ HELP-NO MANUAL MANUAL-NO INITIAL-VALUE)
+
+Create and return a pair of boolean options, suitable for use in an interface.
+
+ This function reduces some of the boilerplate when creating two `option`s for
+ boolean values, e.g. `--foo` and `--no-foo`. It will try to guess at an
+ appropriate name, long option, short option, and result key, but you can
+ override them with the `…-no` keyword options as needed.
+
+ The two options will be returned as two separate values — you can use
+ `defparameters` to conveniently bind them to two separate variables if
+ desired.
+
+ Example:
+
+ (defparameters (*option-debug* *option-no-debug*)
+ (make-boolean-options 'debug
+ :long "debug"
+ :short #d
+ :help "Enable the Lisp debugger."
+ :help-no "Disable the Lisp debugger (the default)."))
+
+ ;; is roughly equivalent to:
+
+ (defparameter *option-debug*
+ (make-option 'debug
+ :long "debug"
+ :short #d
+ :help "Enable the Lisp debugger."
+ :initial-value nil
+ :reduce (constantly t))
+
+ (defparameter *option-no-debug*
+ (make-option 'no-debug
+ :long "no-debug"
+ :short #D
+ :help "Disable the Lisp debugger (the default)."
+ :reduce (constantly nil))
+
+
+
### `MAKE-GROUP` (function)
(MAKE-GROUP NAME &KEY TITLE HELP MANUAL OPTIONS)
--- a/src/main.lisp Thu Jan 30 19:27:27 2020 -0500
+++ b/src/main.lisp Sat Dec 12 18:40:35 2020 -0500
@@ -202,6 +202,107 @@
(typep object 'option))
+(defmacro defparameters (parameters values-form)
+ "Convenience macro for `defparameter`ing multiple variables at once.
+
+ `parameters` must be a list of special variable names suitable for giving to
+ `defparameter`.
+
+ `values-form` must be an expression that returns as many values as parameters
+ in the parameter list. Each parameter will be set to the corresponding value.
+
+ This can be handy when using `make-boolean-options` to create two `option`s at
+ once and assign them to special variables.
+
+ Examples:
+
+ (defparameters (*a* *b*) (truncate 100 3))
+ (list *a* *b*)
+ ; =>
+ ; (33 1)
+
+ (defparameters (*option-foo* *option-no-foo*)
+ (make-boolean-options 'foo
+ :help \"Foo the widgets during the run.\"
+ :help-no \"Do not foo the widgets during the run (the default).\"
+ :long \"foo\"
+ :short #\f))
+
+ "
+ `(progn
+ ,@(loop :for parameter :in parameters
+ :collect `(defparameter ,parameter nil))
+ (setf (values ,@parameters) ,values-form)
+ ',parameters))
+
+(defun make-boolean-options
+ (name &key
+ (name-no (intern (concatenate 'string (string 'no-) (string name))))
+ long
+ (long-no (when long (format nil "no-~A" long)))
+ short
+ (short-no (when short (char-upcase short)))
+ (result-key name)
+ help
+ help-no
+ manual
+ manual-no
+ initial-value)
+ "Create and return a pair of boolean options, suitable for use in an interface.
+
+ This function reduces some of the boilerplate when creating two `option`s for
+ boolean values, e.g. `--foo` and `--no-foo`. It will try to guess at an
+ appropriate name, long option, short option, and result key, but you can
+ override them with the `…-no` keyword options as needed.
+
+ The two options will be returned as two separate values — you can use
+ `defparameters` to conveniently bind them to two separate variables if
+ desired.
+
+ Example:
+
+ (defparameters (*option-debug* *option-no-debug*)
+ (make-boolean-options 'debug
+ :long \"debug\"
+ :short #\d
+ :help \"Enable the Lisp debugger.\"
+ :help-no \"Disable the Lisp debugger (the default).\"))
+
+ ;; is roughly equivalent to:
+
+ (defparameter *option-debug*
+ (make-option 'debug
+ :long \"debug\"
+ :short #\d
+ :help \"Enable the Lisp debugger.\"
+ :initial-value nil
+ :reduce (constantly t))
+
+ (defparameter *option-no-debug*
+ (make-option 'no-debug
+ :long \"no-debug\"
+ :short #\D
+ :help \"Disable the Lisp debugger (the default).\"
+ :reduce (constantly nil))
+
+ "
+ (values (adopt:make-option name
+ :result-key result-key
+ :long long
+ :short short
+ :help help
+ :manual manual
+ :initial-value initial-value
+ :reduce (constantly t))
+ (adopt:make-option name-no
+ :result-key result-key
+ :long long-no
+ :short short-no
+ :help help-no
+ :manual manual-no
+ :reduce (constantly nil))))
+
+
(defclass* group
name title help manual options)