# HG changeset patch # User Steve Losh # Date 1558147655 14400 # Node ID 7f0abaf11050f34666f96a8403c089b242077020 # Parent 860e105446521eaa92fec39dd2e38708db49c47c Add more documentation diff -r 860e10544652 -r 7f0abaf11050 docs/01-usage.markdown --- a/docs/01-usage.markdown Tue Apr 02 17:58:03 2019 -0400 +++ b/docs/01-usage.markdown Fri May 17 22:47:35 2019 -0400 @@ -1,8 +1,8 @@ Usage ===== -Adopt is a simple library for parsing UNIX-style command line arguments in -Common Lisp. It was made because none of the other libraries did what I needed. +Adopt is a library for parsing UNIX-style command line arguments in Common Lisp. +It was made because none of the other libraries did what I needed. [TOC] @@ -16,9 +16,9 @@ Interfaces ---------- -To get started with Adopt, you should create an interface with the -`adopt:make-interface` function. This returns an object representing the -command line interface presented to your users. +To get started with Adopt you can create an interface object with +`adopt:make-interface`. This returns an object representing the command line +interface presented to your users. ### Creating an Interface @@ -33,13 +33,20 @@ :usage "[OPTIONS] PATTERN [FILE]..." :help "Search the contents of each FILE for the regular expression PATTERN. If no files are specified, searches standard input instead.")) -You can now print some help text for your CLI with `adopt:print-help`: +`make-interface` takes several required arguments: + +* `:name` is the name of the program. +* `:summary` is a concise one-line summary of what it does. +* `:usage` is a UNIX-style the command line usage string. +* `:help` is a longer description of the program. + +You can now print some pretty help text for the CLI with `adopt:print-help`: (adopt:print-help *ui*) ; => search - search files for a regular expression - USAGE: … [OPTIONS] PATTERN [FILE]... + USAGE: /path/to/binary [OPTIONS] PATTERN [FILE]... Search the contents of each FILE for the regular expression PATTERN. If no files are specified, searches standard input instead. @@ -47,10 +54,14 @@ ### Line Wrapping Adopt will handle line-wrapping your help text, so you don't need to (and -shouldn't) add extra line breaks when creating your interface. If you want to -line break the text in your source code to fit nicely in your editor, remember -that `adopt:make-interface` is just a function — you can use `format` (possibly -with its `~Newline` directive) to preprocess the help text argument: +shouldn't) add extra line breaks when creating your interface. + +If you want to line break the text in your source code to fit nicely in your +text editor, remember that `adopt:make-interface` is just a function — you can +use `format` (possibly with its [`~Newline` directive][tilde-newline]) to +preprocess the help text argument: + +[tilde-newline]: http://www.lispworks.com/documentation/lw71/CLHS/Body/22_cic.htm (defparameter *ui* (adopt:make-interface @@ -62,19 +73,57 @@ no files are specified, searches ~ standard input instead."))) -Adopt's line-wrapping library [Bobbin][] will only ever *add* line breaks, never -remove them, which means you can include breaks in the output if you want to -have multiple paragraphs in your help text: +If you want to pull out the documentation string into its own variable to keep +that `make-interface` call from getting too unwieldy, you can certainly do that: + + (defparameter *help-text* + (format nil "Search the contents of each FILE for the ~ + regular expression PATTERN. If no files ~ + are specified, searches standard input ~ + instead.")) (defparameter *ui* (adopt:make-interface :name "search" :summary "search files for a regular expression" :usage "[OPTIONS] PATTERN [FILE]..." - :help (format nil - "Search the contents of each FILE for the regular expression PATTERN.~@ - ~@ - If no files are specified (or if - is given as a file name) standard input will be searched instead."))) + :help *help-text*)) + +The `(defparameter … (format nil …))` pattern can be tedious to write, so Adopt +provides a helper macro `define-string` that does exactly that: + + (adopt:define-string *help-text* + "Search the contents of each FILE for the regular ~ + expression PATTERN. If no files are specified, ~ + searches standard input instead.") + + (defparameter *ui* + (adopt:make-interface + :name "search" + :summary "search files for a regular expression" + :usage "[OPTIONS] PATTERN [FILE]..." + :help *help-text*)) + +Adopt's line-wrapping library [Bobbin][] will only ever *add* line breaks, never +remove them, which means you can include breaks in the output if you want to +have multiple paragraphs in your help text. Once again, `format` is your +friend: + +[Bobbin]: https://sjl.bitbucket.io/bobbin/ + + (adopt:define-string *help-text* + "Search the contents of each FILE for the regular ~ + expression PATTERN.~@ + ~@ + If no files are specified (or if - is given as a ~ + file name), standard input will be searched instead.") + + (defparameter *ui* + (adopt:make-interface + :name "search" + :summary "search files for a regular expression" + :usage "[OPTIONS] PATTERN [FILE]..." + :help *help-text*)) If you want to control the width of the help text lines when they are printed, `adopt:print-help` takes a `:width` argument: @@ -89,18 +138,17 @@ expression PATTERN. If no files are specified (or if - is given as a - file name) standard input will be searched + file name), standard input will be searched instead. `adopt:print-help` takes a number of other options — see the API Reference for more information. -### Examples +### Adding Examples Describing the CLI in detail is helpful, but users can often learn a lot more by -seeing a few examples of its usage. `adopt:make-interface` can take an -`:examples` argument, which should be an alist of `(description . example)` -conses: +seeing a few examples of its usage. `make-interface` can take an `:examples` +argument, which should be an alist of `(description . example)` conses: (defparameter *ui* (adopt:make-interface @@ -157,7 +205,7 @@ (adopt:exit) - (adopt:exit 1) + (adopt:exit 1) (adopt:print-help-and-exit *ui*) @@ -171,7 +219,7 @@ These functions are not implemented for every Lisp implementation. PRs are welcome, or you can just write the implementation-specific calls in your program -yourself. +yourself if you prefer. Options ------- @@ -226,13 +274,13 @@ -l, --literal treat PATTERN as a literal string instead of a regular expression -The first argument to `adopt: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 bit, but -it too is required. +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. I prefer to define each option as its own global variable to keep the call to -`adopt:make-interface` from getting too large and unwieldy, but feel free to do +`make-interface` from getting too large and unwieldy, but feel free to do something like this if you prefer to avoid cluttering your package: (defparameter *ui* @@ -247,20 +295,14 @@ ------- At this point we've got an interface with some options, so we can use it to -parse a list of strings we've received as command line arguments: +parse a list of strings we've received as command line arguments with +`adopt:parse-options`: (adopt:parse-options *ui* '("foo.*" "--literal" "a.txt" "b.txt")) ; => ("foo.*" "a.txt" "b.txt") # -`adopt:parse-options` returns two values: a list of non-option arguments, and -a hash table of the option values. - -The keys of the hash table are (by default) the option names given as the first -argument to `adopt:make-option`. We'll see how the option values are determined -soon. - From now on I'll use a special pretty printer for hash tables to make it easier to see what's inside them: @@ -269,11 +311,43 @@ ("foo.*" "a.txt" "b.txt") {LITERAL: T, VERSION: NIL, HELP: NIL} +`parse-options` returns two values: + +1. A list of non-option arguments. +2. An `eql` hash table of the option keys and values. + +We'll talk about how the option values are determined soon. The keys of the +hash table are (by default) the option names given as the first argument to +`make-option`. You can specify a different key for a particular option with the +`:result-key` argument to `make-option`: + + (defparameter *option-literal* + (adopt:make-option 'literal + :result-key 'pattern-is-literal + :long "literal" + :short #\l + :help "treat PATTERN as a literal string instead of a regular expression" + :reduce (constantly t))) + + ;; … + + (adopt:parse-options *ui* '("foo.*" "--literal" "a.txt" "b.txt")) + ; => + ("foo.*" "a.txt" "b.txt") + {PATTERN-IS-LITERAL: T, VERSION: NIL, HELP: NIL} + +This can come in useful if you want multiple options that affect the same result +(e.g. `--verbose` and `--silent` flags that toggle extra log output on and off). + Top-Level Structure ------------------- We'll look at how the option values are computed shortly, but first let's see -the overall structure of the programs you create with Adopt: +the overall structure of the programs you'll typically create with Adopt: + + (defun run (pattern files &key literal) + ;; Actually do something here. + ) (defun toplevel () (handler-case @@ -291,25 +365,525 @@ (error (c) (adopt:print-error-and-exit c)))) - (sb-ext:save-lisp-and-die "search" :toplevel #'toplevel) + (defun build () + (sb-ext:save-lisp-and-die "search" :executable t :toplevel #'toplevel)) + +This is a typical way to use Adopt. There are three functions important +functions here: -The `toplevel` function first uses a `handler-case` to trap all `error`s. If -any error occurs it will print the error message and exit, to avoid confusing -users by dropping them into a Lisp debugger REPL (which they probably won't -understand). When you're developing your program yourself you'll want to omit -this part and let yourself land in the debugger as usual. +* The `toplevel` function takes care of parsing arguments and exiting with an + appropriate status code if necessary. +* The `run` function takes parsed, Lispy arguments and actually *does* + something. When developing (in SLIME, VLIME, etc) you'll call `run`, because + you don't want the program to exit when you're developing interactively. +* The `build` function dumps an executable binary. For more complicated + programs you might use something fancier, like ASDF or Shinmera's Deploy + library instead. -Next we use `adopt:parse-options` to parse the command line arguments and -options. We do some initial checks to see if the user wants `--help` or -`--version` information. If not, we destructure the arguments into the items we -expect and call a `run` function with all the information it needs to do its -job. +In this example the `toplevel` function first uses a `handler-case` to trap all +errors. If any error occurs it will print the error message and exit, to avoid +confusing users by dropping them into a Lisp debugger REPL (which they probably +won't understand). If you're developing a program just for yourself, you might +want to omit this part and let yourself land in the debugger as usual. -If the `destructuring-bind` fails an error will be signaled, and the -`handler-case` will print it and exit. If you want to be a nice person you -could check that the `arguments` have the correct shape first, and return -a friendlier error message to your users if they don't. +Next it uses `adopt:parse-options` to parse the command line arguments and +options. It them does some initial checks to see if the user wants `--help` or +`--version` information. If so, it prints the requested information and exits. + +Otherwise it destructures the arguments into the expected items and calls `run` +with all the information it needs to do its job. If the `destructuring-bind` +fails an error will be signaled, and the `handler-case` will print it and exit. +If you want to be a nice person you could check that the `arguments` have the +correct shape first, and return a friendlier error message to your users if they +don't. Computing Values with Reduce ---------------------------- +So far we've talked about how to define an interface, print help text, parse +a list of options, and the overall structure of the program you'll create with +Adopt. Now we need to talk about how the options the user specifies are parsed +and turned into the resulting hash table. + +Not all command-line options are the same. There are several common types of +options in the UNIX world: + +* Simple options that are either given or not, like `--help` or `--version`. +* Boolean options, like git's `-p/--paginate` and `--no-pager`, where both options affect a single boolean flag. +* Counted options, where the number of times they are given has an effect, like SSH's `-v` option (more `-v`'s means more verbosity). +* Options that take a single parameter, like Mercurial's `--repository /path/to/repo` option, which specifies the path to a repository to work on. +* Options that collect all parameters they are given, like rsync's `--exclude PATTERN`, which you can pass multiple times to add several exclusions. + +An option-parsing library needs to give you the tools to handle all of these +cases (and more). Python's [argparse][ap-actions] library, for example, has +a number of different "actions" to account to handle these various use cases. +Adopt works differently: it uses an interface similar to [reduce][] to let you +do whatever you need. + +[ap-actions]: https://docs.python.org/3/library/argparse.html#action +[reduce]: http://www.lispworks.com/documentation/HyperSpec/Body/f_reduce.htm + +First: before any options are parsed, all entries in the options hash table have +their values set to the `:initial-value` given to `make-option` (or `nil` if +none was specified). + +Next: When you create an option you must specify a `:reduce` function that takes +the current value (and, for options that take a parameter, the given parameter) +and produces a new value each time the option is given. + +You may also specify a `:finally` function that will be called on the final +value after all parsing is done. + +For convenience, if an option takes a parameter you may also specify a `:key` +function, which will be called on the given string before it is passed to the +`:reduce` function. For example: you might use this for an option that takes +integers as arguments with something like `:key #'parse-integer`. + +The combination of these four pieces will let you do just about anything you +might want. Let's look at how to do some common option parsing tasks using +these as our building blocks. + +### Simple Options + +To define an option that just tracks whether it's ever been given, you can do +something like: + + (defparameter *option-help* + (adopt:make-option 'help + :long "help" + :short #\h + :help "display help information and exit" + :initial-value nil + :reduce (lambda (current-value) + (declare (ignore current-value)) + t))) + +But since `nil` is the default initial value and Common Lisp provides the handy +[`constantly`](http://www.lispworks.com/documentation/HyperSpec/Body/f_cons_1.htm) +function, we can do this more concisely: + + (defparameter *option-help* + (adopt:make-option 'help + :long "help" + :short #\h + :help "display help information and exit" + :reduce (constantly t))) + +### Boolean Options + +If we want to have multiple options that both affect the same key in the +results, we can use `:result-key` to do this: + + (defparameter *option-paginate* + (adopt:make-option 'paginate + :long "paginate" + :short #\p + :help "turn pagination on" + :reduce (constantly t))) + + (defparameter *option-no-paginate* + (adopt:make-option 'no-paginate + :result-key 'paginate + :long "no-paginate" + :short #\P + :help "turn pagination off (the default)" + :reduce (constantly nil))) + +The way we've written this, if the user gives multiple options the last-given +one will take precedence. This is generally what you want, because it allows +someone to add a shell alias with these options like this: + + alias g='git --paginate --color=always' + +but still lets them override an option at runtime for a single invocation: + + g --no-paginate log + # expands to: git --paginate --color=always --no-paginate log + +If the last-given option didn't take precedence, they would have to fall back to +the 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. + +### Counting Options + +To define an option that counts how many times it's been given, like SSH's `-v`, +we can say: + + (defparameter *option-verbosity* + (adopt:make-option 'verbosity + :short #\v + :help "output more verbose logs" + :initial-value 0 + :reduce #'1+)) + +### Single-Parameter Options + +To define an option that takes a parameter and only keeps the last one given, we +can do something like: + + (defparameter *option-repository* + (adopt:make-option 'repository + :parameter "PATTERN" + :long "repository" + :short #\R + :help "path to the repository (default .)" + :initial-value "." + :reduce (lambda (prev new) + (declare (ignore prev)) + new))) + +Specifying the `:parameter` argument makes this option a parameter-taking +option, which means the `:reduce` function will be called with the current value +and the given parameter each time. + +Writing that `lambda` out by hand every time would be tedious. Adopt provides +a function called `last` (as in "keep the *last* parameter given") that does +exactly that: + + (defparameter *option-repository* + (adopt:make-option 'repository + :long "repository" + :short #\R + :help "path to the repository (default .)" + :initial-value "." + :reduce #'adopt:last)) + +### Multiple-Parameter Options + +Collecting every parameter given can be done in a number of different ways. One +way could be: + + (defparameter *option-exclude* + (adopt:make-option 'exclude + :long "exclude" + :parameter "PATTERN" + :help "exclude PATTERN (may be given multiple times)" + :initial-value nil + :reduce (lambda (patterns new) + (cons new patterns)))) + +You might notice that the `:reduce` function here is just `cons` with its +arguments flipped. Common Lisp doesn't have a function like Haskell's +[flip](https://en.wikibooks.org/wiki/Haskell/Higher-order_functions#Flipping_arguments), +so Adopt provides it: + + (defparameter *option-exclude* + (adopt:make-option 'exclude + :long "exclude" + :parameter "PATTERN" + :help "exclude PATTERN (may be given multiple times)" + :initial-value nil + :reduce (adopt:flip #'cons))) + +Note that the result of this will be a fresh list of all the given parameters, +but their order will be reversed because `cons` adds each new parameter to the +front of the list. If the order doesn't matter, you're all set. Otherwise, +there are several ways to get around this problem. The first is to add the +parameter to the end of the list in the `:reduce` function: + + (defparameter *option-exclude* + (adopt:make-option 'exclude + :long "exclude" + :parameter "PATTERN" + :help "exclude PATTERN (may be given multiple times)" + :initial-value nil + :reduce (lambda (patterns new) + (append patterns (list new))))) + +This is tedious and inefficient if you have a lot of arguments. If you don't +care much about argument parsing speed, Adopt provides a function called +`collect` that does exactly this, so you don't have to type out that `lambda` +yourself: + + (defparameter *option-exclude* + (adopt:make-option 'exclude + :long "exclude" + :parameter "PATTERN" + :help "exclude PATTERN (may be given multiple times)" + :initial-value nil + :reduce #'adopt:collect)) + +A more efficient (though slightly uglier) solution could be to use `nreverse` at +the end: + + (defparameter *option-exclude* + (adopt:make-option 'exclude + :long "exclude" + :parameter "PATTERN" + :help "exclude PATTERN (may be given multiple times)" + :initial-value nil + :reduce (adopt:flip #'cons) + :finally #'nreverse)) + +If you really need maximum efficiency when parsing command line options (you +probably don't) you could use a queue library, or use a vector and +`vector-push-extend`, or anything else you might dream up. The combination of +`:reduce`, `:initial-value`, and `:finally` will let you do just about anything. + + +Required Options +---------------- + +Adopt doesn't have a concept of a required option. Not only is "required +option" an oxymoron, but it's almost never what you want — if a user types +`program --help` they shouldn't get an error about a missing required option. + +In cases where you really do need to require an option (perhaps only if some +other one is also given) you can check it yourself: + + (defun toplevel () + (handler-case + (multiple-value-bind (arguments options) + (adopt:parse-options *ui*) + (when (gethash 'help options) + (adopt:print-help-and-exit *ui*)) + (unless (gethash 'some-required-option options) + (adopt:print-error-and-exit "Required option foo is missing.")) + (run …)) + (error (c) + (adopt:print-error-and-exit c)))) + +Option Groups +------------- + +Related options can be grouped together in the help text to make them easier for +users to understand. Groups can have their own name, title, and help text. + +Here's a example of how this works. It's fairly long, but shows how Adopt can +help you make a command line interface with all the fixins: + + (defparameter *option-help* + (adopt:make-option 'help + :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" + :long "literal" + :short #\l + :reduce (constantly t))) + + (defparameter *option-no-literal* + (adopt:make-option 'no-literal + :help "treat PATTERN as a regex (the default)" + :long "no-literal" + :short #\L + :result-key 'literal + :reduce (constantly nil))) + + (defparameter *option-case-sensitive* + (adopt:make-option 'case-sensitive + :help "match case-sensitively (the default)" + :long "case-sensitive" + :short #\c + :initial-value t + :reduce (constantly t))) + + (defparameter *option-case-insensitive* + (adopt:make-option 'case-insensitive + :help "ignore case when matching" + :long "case-insensitive" + :short #\C + :result-key 'case-sensitive + :reduce (constantly nil))) + + (defparameter *option-color* + (adopt:make-option '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)" + :long "no-color" + :result-key 'color + :reduce (constantly nil))) + + (defparameter *option-context* + (adopt:make-option 'context + :parameter "N" + :help "show N lines of context (default 0)" + :long "context" + :short #\U + :initial-value 0 + :reduce #'adopt:last + :key #'parse-integer)) + + (defparameter *group-matching* + (adopt:make-group 'matching-options + :title "Matching Options" + :help "These options affect how lines are matched." + :options (list *option-literal* + *option-no-literal* + *option-case-sensitive* + *option-case-insensitive*))) + + (defparameter *group-output* + (adopt:make-group 'output-options + :title "Output Options" + :help "These options affect how matching lines are printed." + :options (list *option-color* + *option-no-color* + *option-context*))) + + (adopt:define-string *help-text* + "Search FILEs for lines that match the regular expression ~ + PATTERN and print them to standard out. Several options ~ + are available to control how the matching lines are printed.~@ + ~@ + If no files are given (or if - is given as a filename) ~ + standard input will be searched.") + + (defparameter *ui* + (adopt:make-interface + :name "search" + :usage "PATTERN [FILE...]" + :summary "print lines that match a regular expression" + :help *help-text* + :contents (list *option-help* + *group-matching* + *group-output*))) + +And with all that out of the way, you've got some nicely-organized help text +for your users: + + (adopt:print-help *ui* :width 60 :option-width 16) + ; => + search - print lines that match a regular expression + + USAGE: /usr/local/bin/sbcl PATTERN [FILE...] + + Search FILEs for lines that match the regular expression + PATTERN and print them to standard out. Several options are + available to control how the matching lines are printed. + + If no files are given (or if - is given as a filename) + standard input will be searched. + + Options: + -h, --help display help and exit + + Matching Options: + -l, --literal treat PATTERN as a literal string + instead of a regex + -L, --no-literal treat PATTERN as a regex (the default) + -c, --case-sensitive + match case-sensitively (the default) + -C, --case-insensitive + ignore case when matching + + Output Options: + --color highlight matches with color + --no-color don't highlight matches (the default) + -U N, --context N show N lines of context (default 0) + +Error Handling +-------------- + +For the most part Adopt doesn't try to be too smart about error handling and +leaves it up to you. + +However, when Adopt is parsing the command line options it *will* signal an +error of type `adopt:unrecognized-option` if the user passes a command line +option that wasn't defined in the interface: + + (defparameter *ui* + (adopt:make-interface + :name "meow" + :summary "say meow" + :usage "[OPTIONS]" + :help "Say meow. Like a cat." + :contents (list + (make-option 'times + :parameter "N" + :long "times" + :initial-value 1 + :help "say meow N times (default 1)" + :reduce #'adopt:last + :key #'parse-integer)))) + + (adopt:parse-options *ui* '("--times" "5")) + ; => + NIL + {TIMES: 5} + + (adopt:parse-options *ui* '("--bark")) + ; => + No such option "--bark". + [Condition of type UNRECOGNIZED-OPTION] + + Restarts: + R 0. DISCARD-OPTION - Discard the unrecognized option. + R 1. TREAT-AS-ARGUMENT - Treat the unrecognized option as a plain argument. + R 2. SUPPLY-NEW-VALUE - Supply a new value to parse. + R 3. RETRY - Retry SLIME REPL evaluation request. + R 4. *ABORT - Return to SLIME's top level. + R 5. ABORT - abort thread (#) + +Adopt provides three possible restarts for this condition as seen above. Adopt +also provides functions with the same names that invoke the restarts properly, +to make it easier to use them programatically with `handler-bind`. For example: + + (handler-bind + ((adopt:unrecognized-option 'adopt:discard-option)) + (adopt:parse-options *ui* '("--bark"))) + ; => + NIL + {TIMES: 1} + + (handler-bind + ((adopt:unrecognized-option 'adopt:treat-as-argument)) + (adopt:parse-options *ui* '("--bark"))) + ; => + ("--bark") + {TIMES: 1} + + (handler-bind + ((adopt:unrecognized-option + (alexandria:rcurry 'adopt:supply-new-value "--times"))) + (adopt:parse-options *ui* '("--bark" "5"))) + ; => + NIL + {TIMES: 5} + +Generating Man Pages +-------------------- + +We've already seen that Adopt can print a pretty help document, but it can also +render `man` pages for you: + + (with-open-file (out "man/man1/search.1" + :direction :output + :if-exists :supersede) + (adopt:print-manual *ui* :stream out)) + +The generated `man` page will contain the same information as the help text by +default. If you want to override this (e.g. to provide a short summary of an +option in the help text, but elaborate more in the manual), you can use the +`:manual` argument to `make-interface` and `make-option`: + + (defparameter *option-exclude* + (adopt:make-option 'exclude + :long "exclude" + :parameter "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))) + +In order for `man` to find the pages, they need to be in the correct place. By +default `man` is usually smart enough to look next to every directory in your +`$PATH` to find a directory called `man`. So if you put your binaries in +`/home/me/bin/` you can put your man pages in `/home/me/man/` under the +appropriate subdirectories and it should all Just Work™. Consult the `man` +documentation for more information. + +Implementation Specifics +------------------------ + +### SBCL + +### ClozureCL diff -r 860e10544652 -r 7f0abaf11050 src/main.lisp --- a/src/main.lisp Tue Apr 02 17:58:03 2019 -0400 +++ b/src/main.lisp Fri May 17 22:47:35 2019 -0400 @@ -413,7 +413,7 @@ (col 0)) (flet ((print-at (c string &optional newline) "Print `string` starting at column `c`, adding padding/newline if needed." - (when (> col c) + (when (>= col c) (terpri stream) (setf col 0)) (format stream "~vA~A" (- c col) #\space string)