adopt/usage/index.html @ 9918f80ae953

adopt: Update site.
author Steve Losh <steve@stevelosh.com>
date Wed, 17 Apr 2019 10:30:37 -0400
parents bcc463ff154f
children c1430240aec7
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>Usage / Adopt</title>
        <link rel="stylesheet" href="../_dmedia/tango.css"/>
        <link rel="stylesheet/less" type="text/css" href="../_dmedia/style.less"/>
        <script src="../_dmedia/less.js" type="text/javascript">
        </script>
    </head>
    <body class="content">
        <div class="wrap">
            <header><h1><a href="..">Adopt</a></h1></header>
                <div class="markdown">
<h1 id="usage"><a href="">Usage</a></h1><p>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.</p>
<div class="toc">
<ul>
<li><a href="#package">Package</a></li>
<li><a href="#interfaces">Interfaces</a><ul>
<li><a href="#creating-an-interface">Creating an Interface</a></li>
<li><a href="#line-wrapping">Line Wrapping</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</li>
<li><a href="#exiting">Exiting</a></li>
<li><a href="#options">Options</a></li>
<li><a href="#parsing">Parsing</a></li>
<li><a href="#top-level-structure">Top-Level Structure</a></li>
<li><a href="#computing-values-with-reduce">Computing Values with Reduce</a></li>
</ul></div>
<h2 id="package">Package</h2>
<p>All core Adopt functions are in the <code>adopt</code> package.  Several of the symbols in
adopt shadow those in the <code>common-lisp</code> package, so you should probably use
namespaced <code>adopt:…</code> symbols instead of <code>USE</code>ing the package.</p>
<h2 id="interfaces">Interfaces</h2>
<p>To get started with Adopt, you should create an interface with the
<code>adopt:make-interface</code> function.  This returns an object representing the
command line interface presented to your users.</p>
<h3 id="creating-an-interface">Creating an Interface</h3>
<p>Let's say you're developing a program to search the contents of files (because
the world certainly needs <em>another</em> <code>grep</code> replacement).  You might start with
something like:</p>
<div class="codehilite"><pre><span/>(defparameter *ui*
  (adopt:make-interface
    :name "search"
    :summary "search files for a regular expression"
    :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."))
</pre></div>


<p>You can now print some help text for your CLI with <code>adopt:print-help</code>:</p>
<div class="codehilite"><pre><span/>(adopt:print-help *ui*)
; =&gt;
search - search files for a regular expression

USAGE: … [OPTIONS] PATTERN [FILE]...

Search the contents of each FILE for the regular expression PATTERN.  If no
files are specified, searches standard input instead.
</pre></div>


<h3 id="line-wrapping">Line Wrapping</h3>
<p>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 <code>adopt:make-interface</code> is just a function — you can use <code>format</code> (possibly
with its <code>~Newline</code> directive) to preprocess the help text argument:</p>
<div class="codehilite"><pre><span/>(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, searches ~
                       standard input instead.")))
</pre></div>


<p>Adopt's line-wrapping library [Bobbin][] will only ever <em>add</em> 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:</p>
<div class="codehilite"><pre><span/>(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.")))
</pre></div>


<p>If you want to control the width of the help text lines when they are printed,
<code>adopt:print-help</code> takes a <code>:width</code> argument:</p>
<div class="codehilite"><pre><span/>(adopt:print-help *ui* :width 50)
; =&gt;
search - search files for a regular expression

USAGE: … [OPTIONS] PATTERN [FILE]...

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.
</pre></div>


<p><code>adopt:print-help</code> takes a number of other options — see the API Reference for
more information.</p>
<h3 id="examples">Examples</h3>
<p>Describing the CLI in detail is helpful, but users can often learn a lot more by
seeing a few examples of its usage.  <code>adopt:make-interface</code> can take an
<code>:examples</code> argument, which should be an alist of <code>(description . example)</code>
conses:</p>
<div class="codehilite"><pre><span/>(defparameter *ui*
  (adopt:make-interface
    :name "search"
    :summary "search files for a regular expression"
    :usage "[OPTIONS] PATTERN [FILE]..."
    :help …
    :examples
    '(("Search foo.txt for the string 'hello':"
       . "search hello foo.txt")
      ("Search standard input for lines starting with x:"
       . "search '^x' -")
      ("Watch the file log.txt for lines containing the username steve.losh:"
       . "tail foo/bar/baz/log.txt | search --literal steve.losh -"))))

(adopt:print-help *ui* :width 50)
; =&gt;
search - search files for a regular expression

USAGE: … [OPTIONS] PATTERN [FILE]...

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.

Examples:

  Search foo.txt for the string 'hello':

      search hello foo.txt

  Search standard input for lines starting with x:

      search '^x' -

  Watch the file log.txt for lines containing the
  username steve.losh:

      tail foo/bar/baz/log.txt | search --literal steve.losh -
</pre></div>


<p>Notice how Adopt line wraps the prose explaining each example, but leaves the
example itself untouched for easier copying and pasting.  In general Adopt tries
to do the right thing for your users (even when that means making a little more
work for <em>you</em> in certain places).</p>
<h2 id="exiting">Exiting</h2>
<p>Adopt provides some helpful utility functions to exit out of your program with
a UNIX exit code.  These do what you think they do:</p>
<div class="codehilite"><pre><span/>(adopt:exit)

(adopt:exit 1)

(adopt:print-help-and-exit *ui*)

(adopt:print-help-and-exit *ui*
  :stream *error-output*
  :exit-code 1)

(handler-case (assert (= 1 0))
  (error (err)
    (adopt:print-error-and-exit err)))
</pre></div>


<p>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.</p>
<h2 id="options">Options</h2>
<p>Now that you know how to create an interface, you can create some options to use
inside it with <code>adopt:make-option</code>:</p>
<div class="codehilite"><pre><span/>(defparameter *option-version*
  (adopt:make-option 'version
    :long "version"
    :help "display version information and exit"
    :reduce (constantly t)))

(defparameter *option-help*
  (adopt:make-option 'help
    :long "help"
    :short #\h
    :help "display help information 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"
    :reduce (constantly t)))

(defparameter *ui*
  (adopt:make-interface
    :name "search"
    :summary "search files for a regular expression"
    :usage "[OPTIONS] PATTERN [FILE]..."
    :help "Search the contents of …"
    :contents (list
                *option-version*
                *option-help*
                *option-literal*)))
</pre></div>


<p>Adopt will automatically add the options to the help text:</p>
<div class="codehilite"><pre><span/>(adopt:print-help *ui*)
; =&gt;
search - search files for a regular expression

USAGE: /usr/local/bin/sbcl [OPTIONS] PATTERN [FILE]...

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
</pre></div>


<p>The first argument to <code>adopt:make-option</code> is the name of the option, which we'll
see put to use shortly.  At least one of <code>:short</code> and <code>:long</code> is required, and
<code>:help</code> text must be specified.  We'll talk more about <code>:reduce</code> in a bit, but
it too is required.</p>
<p>I prefer to define each option as its own global variable to keep the call to
<code>adopt:make-interface</code> from getting too large and unwieldy, but feel free to do
something like this if you prefer to avoid cluttering your package:</p>
<div class="codehilite"><pre><span/>(defparameter *ui*
  (adopt:make-interface

    :contents
    (list (adopt:make-option 'foo …)
          (adopt:make-option 'bar …)
          …)))
</pre></div>


<h2 id="parsing">Parsing</h2>
<p>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:</p>
<div class="codehilite"><pre><span/>(adopt:parse-options *ui* '("foo.*" "--literal" "a.txt" "b.txt"))
; =&gt;
("foo.*" "a.txt" "b.txt")
#&lt;HASH-TABLE :TEST EQL :COUNT 3 {10103142A3}&gt;
</pre></div>


<p><code>adopt:parse-options</code> returns two values: a list of non-option arguments, and
a hash table of the option values.</p>
<p>The keys of the hash table are (by default) the option names given as the first
argument to <code>adopt:make-option</code>.  We'll see how the option values are determined
soon.</p>
<p>From now on I'll use a special pretty printer for hash tables to make it easier
to see what's inside them:</p>
<div class="codehilite"><pre><span/>(adopt:parse-options *ui* '("foo.*" "--literal" "a.txt" "b.txt"))
; =&gt;
("foo.*" "a.txt" "b.txt")
{LITERAL: T, VERSION: NIL, HELP: NIL}
</pre></div>


<h2 id="top-level-structure">Top-Level Structure</h2>
<p>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:</p>
<div class="codehilite"><pre><span/>(defun toplevel ()
  (handler-case
      (multiple-value-bind (arguments options)
          (adopt:parse-options *ui*)
        (when (gethash 'help options)
          (adopt:print-help-and-exit *ui*))
        (when (gethash 'version options)
          (format t "1.0.0~%")
          (adopt:exit))
        (destructuring-bind (pattern . files) arguments
          (run pattern
               files
               :literal (gethash 'literal options))))
    (error (c)
      (adopt:print-error-and-exit c))))

(sb-ext:save-lisp-and-die "search" :toplevel #'toplevel)
</pre></div>


<p>The <code>toplevel</code> function first uses a <code>handler-case</code> to trap all <code>error</code>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.</p>
<p>Next we use <code>adopt:parse-options</code> to parse the command line arguments and
options.  We do some initial checks to see if the user wants <code>--help</code> or
<code>--version</code> information.  If not, we destructure the arguments into the items we
expect and call a <code>run</code> function with all the information it needs to do its
job.</p>
<p>If the <code>destructuring-bind</code> fails an error will be signaled, and the
<code>handler-case</code> will print it and exit.  If you want to be a nice person you
could check that the <code>arguments</code> have the correct shape first, and return
a friendlier error message to your users if they don't.</p>
<h2 id="computing-values-with-reduce">Computing Values with Reduce</h2>
                </div>
            <footer><p><i>Made with Lisp and love by <a href="http://stevelosh.com/">Steve Losh</a>.</i></p>
<p><a href="http://rochestermade.com" title="Rochester Made"><img src="http://rochestermade.com/media/images/rochester-made-dark-on-light.png" alt="Rochester Made" title="Rochester Made"/></a></p>

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-15328874-3', 'auto');
  ga('send', 'pageview');

</script></footer>
        </div>
    </body>
</html>