<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Dark Chestnut</title>
    <description>Deploying Common Lisp production software.
</description>
    <link>https://www.darkchestnut.com/</link>
    <atom:link href="https://www.darkchestnut.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Tue, 20 May 2025 20:09:04 +0000</pubDate>
    <lastBuildDate>Tue, 20 May 2025 20:09:04 +0000</lastBuildDate>
    <generator>Jekyll v4.2.2</generator>
    
      <item>
        <title>Set up Verbose for multi-threaded standalone applications.</title>
        <description>&lt;p&gt;Although Verbose is one of few logging libraries that work with threaded
applications (See &lt;a href=&quot;https://sabracrolleton.github.io/logging-comparison#functionality-comparisons&quot;&gt;Comparison of Common Lisp Logging Libraries&lt;/a&gt;), I had
some trouble getting it to work in my application. I have a Hunchentoot web
application which handles each request in a separate thread that is built as a
standalone executable. Getting Verbose to work in Slime was trivial but once I
built the standalone, it kept crashing.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://shinmera.github.io/verbose/&quot;&gt;Verbose&lt;/a&gt; documentation provides all
the information needed to make this setup work but not in a step-by-step
fashion so this took me some time to figure out.&lt;/p&gt;

&lt;p&gt;To work with threaded applications Verbose must run inside a thread of its
own. It tries to make life easier for the majority case by starting its thread
as soon as it is loaded. Creating a standalone application requires that the
running lisp image contains only a single running thread. The Verbose
background thread prevents the binary from being built. This can be remedied by
preventing Verbose from immediately starting its background thread and then
manually start it inside the application.&lt;/p&gt;

&lt;p&gt;When Verbose is loaded inside Slime it prints to the REPL’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*standard-output*&lt;/code&gt;
without fuss but when I loaded it inside my standalone binary it caused the
application to crash. I did not investigate the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*standard-output*&lt;/code&gt; connection
logic but I discovered that you must tell Verbose explicitly about the current
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*standard-output*&lt;/code&gt; in a binary otherwise it won’t work.&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(pushnew :verbose-no-init *features*)&lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;This feature must be set before the Verbose system is loaded. It prevents
Verbose from starting its main background thread, which it does by default
immediately when it is loaded.&lt;/p&gt;

    &lt;p&gt;I added this form in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.asd&lt;/code&gt; file immediately before my application
system definition. While executing code inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.asd&lt;/code&gt; file is considered
bad style it provided the cleanest way for me to do this otherwise I would
have to do it in multiple places to cover all the use cases for development
flows and building the production binary. There may be a better way to set
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*features*&lt;/code&gt; before a system is loaded but I have not yet discovered it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(v:output-here *standard-output*)&lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;This form makes Verbose use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*standard-output*&lt;/code&gt; as it currently
 exists. Leaving out this line was the cause of my application crashes. I am
 not sure what the cause is but I suspect Verbose tries to use Slime’s
 version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*standard-output*&lt;/code&gt; if you don’t tell it otherwise, even when it
 is not running in Slime.&lt;/p&gt;

    &lt;p&gt;This must be done before starting the Verbose background thread.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(v:start v:*global-controller*)&lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;Start the Verbose background thread.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(v:info :main &quot;Hello world!&quot;)&lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;Start logging.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I use systemd to run my applications. Systemd recommends that applications run
in the foreground and print logs to the standard output. The application output
is captured and logged in whichever way systemd is configured. On default
installations this is usually in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/log/syslog&lt;/code&gt; in the standard logging
format which prepends the timestamp and some other information. Verbose also by
default prints the timestamp in the logged message, which just adds noise and
makes syslog difficult to read.&lt;/p&gt;

&lt;p&gt;Verbose’s logging format can be configured to be any custom format by
subclassing its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt; class and providing the proper formatting
method. This must be done before any other Verbose configuration.&lt;/p&gt;

&lt;p&gt;Combining all the code looks like below.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.asd&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pushnew&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verbose-no-init&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*features*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defsystem&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;#:app&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.lisp&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defclass&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log-message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defmethod&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;v:format-message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log-message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;[~5,a] ~{&amp;lt;~a&amp;gt;~} ~a&quot;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:level&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:categories&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:format-message&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;NIL&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:content&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;v:*default-message-class*&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;log-message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:output-here&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*standard-output*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:start&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;v:*global-controller*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;v:info&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:main&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello world!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Sat, 20 Nov 2021 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2021/set-up-verbose-multi-threaded-standalone-applications/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2021/set-up-verbose-multi-threaded-standalone-applications/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
      <item>
        <title>User feedback during long running external processes.</title>
        <description>&lt;p&gt;Sometimes it may be necessary to execute an external command that takes a long
time to complete, long enough that the user needs visual feedback while it is
running to show that the process is still alive.&lt;/p&gt;

&lt;p&gt;UIOP provides fantastic tools for running external commands in a portable
manner but it was not obvious to me how to show the external command’s output
to the user while it was still busy. I also wanted to execute the external
command in a synchronous fashion, i.e. my lisp application must wait for the
external command to finish before continuing. The need for synchronicity sent
me down the wrong path of using the synchronous &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uiop:run-program&lt;/code&gt;. It only
returns when the external command has finished, which means you can only
process the command output once it is completed.&lt;/p&gt;

&lt;p&gt;I eventually realised I should use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uiop:launch-program&lt;/code&gt;, the asynchronous
version, and I came up with the following solution. In the example below the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(ping)&lt;/code&gt; function pings a website and prints the results as they become
available. Afterwards it returns the exit code of the ping command.&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ping&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exit-code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;unwind-protect&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;progn&lt;/span&gt;
           &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:launch-program&lt;/span&gt;
                       &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ping&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-c&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;5&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;www.darkchestnut.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                       &lt;span class=&quot;ss&quot;&gt;:ignore-error-status&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;
                       &lt;span class=&quot;ss&quot;&gt;:output&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
           &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:process-info-output&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
           &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;iter&lt;/span&gt;
             &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:process-alive-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
             &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;iter&lt;/span&gt;
               &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
               &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;write-char&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read-char&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*STANDARD-OUTPUT*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
             &lt;span class=&quot;c1&quot;&gt;;; ... Maybe do something here&lt;/span&gt;
             &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
           &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:copy-stream-to-stream&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*STANDARD-OUTPUT*&lt;/span&gt; 
                                       &lt;span class=&quot;ss&quot;&gt;:linewise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exit-code&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:wait-process&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:close-streams&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;exit-code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the first example the command’s output is shown to the user but it is not
processed in any other way. If you need to do some extra processing on it after
completion then the next example should provide a good starting point.&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ping-processing&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exit-code&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;with-open-stream&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;output-stream&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;make-string-output-stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;with-open-stream&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;broadcast-stream&lt;/span&gt; 
                         &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;make-broadcast-stream&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*STANDARD-OUTPUT*&lt;/span&gt; 
                                                &lt;span class=&quot;nv&quot;&gt;output-stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;unwind-protect&lt;/span&gt;
             &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;progn&lt;/span&gt;
               &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:launch-program&lt;/span&gt;
                           &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ping&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-c&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;5&quot;&lt;/span&gt; 
                                 &lt;span class=&quot;s&quot;&gt;&quot;www.darkchestnut.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                           &lt;span class=&quot;ss&quot;&gt;:ignore-error-status&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;
                           &lt;span class=&quot;ss&quot;&gt;:output&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
               &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:process-info-output&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
               &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;iter&lt;/span&gt;
                 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:process-alive-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
                 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;iter&lt;/span&gt;
                   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
                   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;write-char&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read-char&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;broadcast-stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
                 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
               &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:copy-stream-to-stream&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;broadcast-stream&lt;/span&gt; 
                                           &lt;span class=&quot;ss&quot;&gt;:linewise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exit-code&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:wait-process&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;uiop:close-streams&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;get-output-stream-string&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;output-stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;;; ... process output here&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;exit-code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Sun, 02 May 2021 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2021/user-feedback-long-running-external-processes/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2021/user-feedback-long-running-external-processes/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
      <item>
        <title>Process sub-command style command line options with Adopt.</title>
        <description>&lt;p&gt;How to process sub-command style command line arguments is a question that
arises more and more. Many of the basic option handling libraries can not
handle this at all, or they make it very difficult to do so.&lt;/p&gt;

&lt;p&gt;One of the newer libraries in the option processing field is &lt;a href=&quot;https://docs.stevelosh.com/adopt/&quot;&gt;Adopt&lt;/a&gt; by
Steve Losh. It was not designed to handle sub-commands but it is in fact very
capable to do this without having to jump through too many hoops.&lt;/p&gt;

&lt;p&gt;In a Reddit &lt;a href=&quot;https://old.reddit.com/r/Common_Lisp/comments/m7gjno/writing_small_cli_programs_in_common_lisp_steve/grdqq1j/&quot;&gt;thread&lt;/a&gt; someone asked if Adopt can handle sub-command
processing and Steve answered with the following example:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;eval-when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:compile-toplevel&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:load-toplevel&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ql:quickload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:adopt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:silent&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defpackage&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:subex&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:use&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:cl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:export&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:toplevel&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;in-package&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:subex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;;;;; Global Options and UI ----------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/help*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;help&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:long&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;help&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;display help and exit&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:reduce&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;constantly&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/version*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;version&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:long&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;version&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;display version and exit&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:reduce&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;constantly&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/main*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-interface&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;subex&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:usage&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;[subcommand] [options]&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;subcommand example program&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:summary&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;an example program that uses subcommands&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:contents&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/help*&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/version*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui*&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/main*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;;;;; Subcommand Foo -----------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/foo/a*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:result-key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;mode&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:short&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;#\a&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;run foo in mode A&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:reduce&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;constantly&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/foo/b*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;b&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:result-key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;mode&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:short&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;#\b&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;run foo in mode B&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:reduce&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;constantly&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/foo*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-interface&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;subex foo&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:usage&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;foo [-a|-b]&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:summary&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;foo some things&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;foo some things&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:contents&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/foo/a*&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/foo/b*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;run/foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Running foo in ~A mode.~%&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;;;;; Subcommand Bar -----------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/bar/meow*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;meow&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:long&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;meow&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;meow loudly after each step&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:reduce&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;constantly&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defparameter&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/bar*&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:make-interface&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;subex bar&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:usage&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bar [--meow] FILE...&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:summary&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bar some files&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:help&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bar some files&quot;&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:contents&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*o/bar/meow*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;run/bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;paths&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;meow?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dolist&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bar-ing ~A.~%&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;meow?&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;write-line&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;meow.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;;;;; Toplevel -----------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;toplevel/foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;multiple-value-bind&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:parse-options-or-exit&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/foo*&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Foo does not take arguments, got ~S&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;run/foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;gethash&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;mode&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;toplevel/bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;multiple-value-bind&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:parse-options-or-exit&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/bar*&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bar requires arguments, got none.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;run/bar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;gethash&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;meow&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lookup-subcommand&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cond&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/main*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;#&apos;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;toplevel/foo&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/foo*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;#&apos;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;toplevel/bar&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/bar*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Unknown subcommand ~S&quot;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;toplevel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;sb-ext:disable-debugger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;multiple-value-bind&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;global-options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;handler-bind&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:unrecognized-option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;adopt:treat-as-argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:parse-options&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*ui/main*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;gethash&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;version&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;global-options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;write-line&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;multiple-value-bind&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;subtoplevel&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;lookup-subcommand&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;subtoplevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;gethash&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;help&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;global-options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;adopt:print-help-and-exit&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;funcall&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;subtoplevel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rest&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Mon, 19 Apr 2021 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2021/process-subcommand-style-command-line-arguments-adopt/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2021/process-subcommand-style-command-line-arguments-adopt/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
      <item>
        <title>A list of Common Lisp command line argument parsers.</title>
        <description>&lt;p&gt;I was searching for a command line option parser that can handle git-style
sub-commands and found a whole bunch of libraries. It appears as if libraries
on this topic proliferate more than usual.&lt;/p&gt;

&lt;p&gt;I evaluated them only to the point where I could decide to skip it or give it a
cursory test. The information I gathered is summarised below.&lt;/p&gt;

&lt;p&gt;If you only need the usual flag and option processing, i.e. not sub-commands,
then I would suggest &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unix-opts&lt;/code&gt;. It appears to be the accepted standard and is
actively maintained. It is also suggested by both &lt;a href=&quot;https://github.com/CodyReichert/awesome-cl#command-line-options-parsers&quot;&gt;Awesome Common Lisp&lt;/a&gt;
and the &lt;a href=&quot;https://lisp-journey.gitlab.io/blog/state-of-the-common-lisp-ecosystem-2020/&quot;&gt;State of the Common Lisp Ecosystem Survey 2020&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If your needs are very complex or specific you can investigate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clon&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;utility-arguments&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ace.flag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For basic flags and options with sub-commands, there are a few libraries that
explicitly support sub-command processing but you should be able to make it
work with many of the other options and a bit of additional code.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Name&lt;/th&gt;
      &lt;th&gt;Print help&lt;/th&gt;
      &lt;th&gt;Native sub-commands&lt;/th&gt;
      &lt;th&gt;Notes&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ace.flag&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;Not in QL.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;adopt&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Can generate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man&lt;/code&gt; files.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;apply-argv&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Does not handle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-xyz&lt;/code&gt; as three flags.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;cl-just-getopt-parser&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Easy to use.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;cl-cli&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;cl-argparse&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;cli-parser&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Does not handle free arguments, not in QL.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;clon&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;Very complex, most feature rich.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;command-line-arguments&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;Not well documented.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;getopt&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Does not handle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-xyz&lt;/code&gt; as three flags, not well documented.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;parse-args&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Not in QL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;utility-arguments&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;Complex to set up&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;unix-options&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Easy to use.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;unix-opts&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;The standard recommendation.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

</description>
        <pubDate>Sun, 18 Apr 2021 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2021/list-command-line-argument-parsers/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2021/list-command-line-argument-parsers/</guid>
        
        
      </item>
    
      <item>
        <title>Database drivers for PostgreSQL and SQLite.</title>
        <description>&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;To interact with a database system from your code requires some database driver
library. For most database systems there are multiple driver libraries, most of
which are stable and work well.&lt;/p&gt;

&lt;p&gt;There are also options of multi-system and single-system drivers. The
multi-system drivers can interface with multiple database systems, usually
MySQL, PostgreSQL and SQLite, while single-system drivers only work for a
specific database system.&lt;/p&gt;

&lt;p&gt;The two main reasons I see for using a multi-system driver are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You want to reduce the risk in the event you need to switch your database
system mid-project. It is rare but possible.&lt;/li&gt;
  &lt;li&gt;You work on multiple projects which use different database systems. With a
multi-system driver you only need to learn one library which works the same
for all databases.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My response to those two reasons are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I consider it extremely unlikely that I will need to switch database systems
for the project.&lt;/li&gt;
  &lt;li&gt;I prefer to use libraries that do one thing well rather than multi-purpose
ones. I would rather learn the focused driver for each different database
system I use than fighting the unavoidable complexities which come with
generalised tools that cater for databases I will never use.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In another &lt;a href=&quot;/2020/picking-database-web-application/&quot;&gt;post&lt;/a&gt; I mentioned that I use PostgreSQL for my application
database. I also use SQLite for local configuration files. The drivers I
considered are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;CL-sxl&lt;/li&gt;
  &lt;li&gt;Sxql&lt;/li&gt;
  &lt;li&gt;CL-dbi&lt;/li&gt;
  &lt;li&gt;CL-sql&lt;/li&gt;
  &lt;li&gt;Postmodern&lt;/li&gt;
  &lt;li&gt;CL-sqlite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Postmodern and CL-sqlite are the only single-system drivers in that list. Due
to the two reasons I mentioned above, I use Postmodern and CL-sqlite.&lt;/p&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

</description>
        <pubDate>Mon, 13 Jul 2020 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2020/database-drivers-postgresql-sqlite/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2020/database-drivers-postgresql-sqlite/</guid>
        
        
      </item>
    
      <item>
        <title>Database migration libraries for PostgreSQL.</title>
        <description>&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;It may be tempting at the start of a new project to create the first database
tables manually, or write SQL scripts that you run manually, especially when
you first have to spend a significant amount of time on sifting through all the
migration libraries and then some more to get it working properly.&lt;/p&gt;

&lt;p&gt;Going through this process did slow me down at the start of the project but I
was determined to use a migration tool because hunting inexplicable bugs that
only happen in production just to find out there is a definition mismatch
between the production and development databases is not fun. Using such a tool
also motivates you to write both the setup and teardown steps for each table
while the current design is still fresh in your mind.&lt;/p&gt;

&lt;p&gt;At first I considered a standalone migration tool because I expect them to be
very good at that single task. However, learning the idiosyncrasies of a new
tool and trying to make it fit seamlessly into my development workflow seemed
like more trouble than it is worth.&lt;/p&gt;

&lt;p&gt;I decided to stick with a Common Lisp library and found the following seven that
work with PostgreSQL and/or Postmodern:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/fisxoj/postmodern-passenger-pigeon&quot;&gt;Postmodern-passenger-pigeon&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;database-migrations&lt;/li&gt;
  &lt;li&gt;cl-migrations&lt;/li&gt;
  &lt;li&gt;Mito&lt;/li&gt;
  &lt;li&gt;Crane&lt;/li&gt;
  &lt;li&gt;Orizuru-orm&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/Masayukiii/cl-mgr&quot;&gt;cl-mgr&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I quickly discounted Crane and Mito because they are ORM (Object Relational
Mapper) libraries which are way more complex than a dedicated migration
library. Development on Crane have stalled some time ago and I don’t feel it is
mature enough for frictionless use yet. Mito declares itself as being in Alpha
state; also not mature enough yet.&lt;/p&gt;

&lt;p&gt;I only stumbled onto cl-mgr and Orizuru-orm long after making my decision so I
did not investigate them seriously. Orizuru-orm is in any case an ORM which I
would have discounted because it is too complex for my needs. CL-mgr looks
simple, which is a good thing. It is based on cl-dbi which makes it a good
candidate if you foresee switching databases but even if I discovered it sooner
I would have discounted it for the same reason as CL-migrations.&lt;/p&gt;

&lt;p&gt;CL-migrations looks very promising. It is a simple library focusing only on
migrations. It uses clsql to interface with the database which bothered me
because I already committed to using Postmodern and I try to avoid adding a lot
of unused code to my projects. The positive side is that it interfaces to many
different databases so it is a good candidate if you are not committed to using
Postmodern. It is also a stable code base with no outstanding bug reports.&lt;/p&gt;

&lt;p&gt;The two projects I focused on was Postmodern-passenger-pigeon and
Database-migrations because they both use Postmodern for a database interface.&lt;/p&gt;

&lt;p&gt;Postmodern-passenger-pigeon was in active development at the time and it seemed
safer to use than Database-migrations because it can do dry runs, which is a
very nice feature when you are upgrading your production database and face the
possibility of losing data when things go awry. Unfortunately I could not get
it working within a reasonable amount of time.&lt;/p&gt;

&lt;p&gt;I finally settled on Database-migrations. It is a small code base, focused on
one task, it is mature and it uses Postmodern so it does not pull in a whole
new database interface into my project. There are however some less positive
issues.&lt;/p&gt;

&lt;p&gt;The first issue is a hindrance during development. Every time the migrations
ASDF system (or the file containing it, as ASDF prefers that all systems be
defined in a single file) is recompiled it adds all the defined migrations to
the migrations list. Though each one will only be applied once to the DB it is
still bothersome. One can then clear the list with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(setf
database-migrations::*migrations* nil)&lt;/code&gt; but then only newly modified migration
files will be added. The solution then is to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touch&lt;/code&gt; the .asd file after
clearing the migrations list.&lt;/p&gt;

&lt;p&gt;The second negative point is quite dangerous. The downgrade function takes a
target version as parameter, with a default target of 0. This means that if you
execute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;downgrade&lt;/code&gt; without specifying a target version you delete your whole
database.&lt;/p&gt;

&lt;p&gt;I am currently using Database-migrations and it works well for me. If for some
reason I need to switch I will use cl-migrations.&lt;/p&gt;

&lt;h2 id=&quot;using-database-migrations&quot;&gt;Using Database-migrations&lt;/h2&gt;

&lt;p&gt;To address the danger of unintentionally deleting my database I created a
wrapper function that does both upgrade and downgrade, and it requires a target
version number.&lt;/p&gt;

&lt;p&gt;Another practical issue I discovered is that upgrades and downgrades happen in
the same order as they are defined in the migration file. If you create two
tables in a single file where table 2 depends on table 1 then you can not
revert / downgrade because Database-migrations will attempt to delete table 1
before table 2. The solution here is to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def-queries-migration&lt;/code&gt; macro
(instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def-query-migration&lt;/code&gt;) which defines multiple queries
simultaneously . If you get overwhelmed by a single definition that defines
multiple tables the other option is to stick with one migration definition per
file.&lt;/p&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

</description>
        <pubDate>Mon, 27 Apr 2020 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2020/database-migration-libraries-postgresql/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2020/database-migration-libraries-postgresql/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
      <item>
        <title>Web development starter pack.</title>
        <description>&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;A big part of a web development framework’s value is that it pre-selects a set
of common libraries. If you use the framework as-is you will have a reasonable
setup to build on without having to put any thought into it. More comprehensive
frameworks express opinions on many more issues in addition to libraries and
they often make it difficult to stray from their prescribed path. Lightweight
frameworks confine their opinions to fewer topics and usually make it easier to
follow your own path.&lt;/p&gt;

&lt;p&gt;In this blog &lt;a href=&quot;/2019/picking-libraries-web-development/&quot;&gt;series&lt;/a&gt; I describe a list of libraries I picked for doing my web
development. The list of libraries can be treated as a featherweight framework,
a good foundation to make a quick start but with no fences to keep you on any
particular path. The only exceptions are when a library depends on another
library, for example easy-routes only works with Hunchentoot. Replacing
Hunchentoot will require a replacement for easy-routes.&lt;/p&gt;

&lt;p&gt;Below is the list of libraries I use. The options I considered and my reasoning
for picking these particular libraries are set out in the various posts in the
series.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt; : PostgreSQL&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DB access&lt;/strong&gt; : Postmodern&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ORM / DAO&lt;/strong&gt; : Postmodern&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DB migrations&lt;/strong&gt; : Database-migrations&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP server&lt;/strong&gt; : Hunchentoot&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request routing and middleware&lt;/strong&gt; : Easy-routes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON&lt;/strong&gt; : ST-JSON&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Templating&lt;/strong&gt; : Djula&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt; : 5am&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging&lt;/strong&gt; : Verbose&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session management&lt;/strong&gt; : Override Hunchentoot’s sessions with a &lt;a href=&quot;/2018/hunchentoot_custom_sessions/&quot;&gt;custom&lt;/a&gt; class
to store session data in PostgreSQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Password hashing&lt;/strong&gt; : cl-pass&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration files&lt;/strong&gt; : Sqlite&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building binaries&lt;/strong&gt; : Buildapp with CCL&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI parameters&lt;/strong&gt; : unix-opts&lt;/p&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

</description>
        <pubDate>Wed, 15 Apr 2020 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2020/web-development-starter-pack/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2020/web-development-starter-pack/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
      <item>
        <title>Picking the database for a web application.</title>
        <description>&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;Picking a database for your web application can be surprisingly time
consuming. There are a variety of databases of various types available and
Quicklisp has an interface library for many of them.&lt;/p&gt;

&lt;p&gt;Unless you need a very specific capability from the database and you are
acutely aware of this fact, chances are that a standard relational database
will be more than sufficient to provide for your needs.&lt;/p&gt;

&lt;p&gt;Once you have decided to use a SQL database the three main contenders are
MySQL, PostgreSQL and SQLite. They are all open-source, mature, actively
developed and well-supported by Common Lisp.&lt;/p&gt;

&lt;p&gt;For most projects SQLite will work very well, even in production. It can
&lt;a href=&quot;https://sqlite.org/whentouse.html&quot;&gt;handle&lt;/a&gt; database sizes and query volumes
far exceeding what most web apps will ever need, it stores the whole database
in a single file so the backup process is simply a normal file copy and there
is no database server application to configure and maintain.&lt;/p&gt;

&lt;p&gt;The down side of SQLite is that the database file must be stored on the same
machine as where the application is running because it is not a client/server
system. If you need to run the application and the database on separate
machines then SQLite will not work. One such reason would be if you need to
build a highly available service, i.e. your application must run on multiple
machines to ensure it remains available when one server stops working.&lt;/p&gt;

&lt;p&gt;If you decide that you need a client/server database it comes down to MySQL or
PostgreSQL. This is mostly a choice of personal preference. Both have very good
support in Common Lisp. There are areas in which one is better than the other
but few applications will ever run into those cases.&lt;/p&gt;

&lt;p&gt;I use PostgreSQL in my projects, both for development and production
environments. The idea is sometimes put forward that one should use SQLite in
development and PostgreSQL in production. This is a bad idea. Even though both
implement the SQL standard there are still distinct differences in how they
function and in their capabilities. By using different database systems you
must cater for two database variants instead of one. In addition, you will
always go to production with untested code that can only be tested in
production.&lt;/p&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;a class=&quot;alert-link&quot; href=&quot;/2019/picking-libraries-web-development/&quot;&gt;
    Index of picking libraries blog series&lt;/a&gt;
&lt;/div&gt;

</description>
        <pubDate>Wed, 01 Apr 2020 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2020/picking-database-web-application/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2020/picking-database-web-application/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
      <item>
        <title>Testing command line interfaces</title>
        <description>&lt;p&gt;When you make a standalone application intended as a command line tool you may
want integration tests to exercise the application through the normal command
line user interface.&lt;/p&gt;

&lt;p&gt;There are tools that can do this testing on OS executable applications but
sometimes it is useful to run these tests as part of the test suite without
having to first build a standalone binary.&lt;/p&gt;

&lt;p&gt;The goal then is to test a function that only interacts via standard input and
output. This code demonstrates how it can be done:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;; Prompt 1&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*query-io*&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Name: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read-line&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*query-io*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;; Prompt 2&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*query-io*&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Greeting: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;greet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read-line&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*query-io*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;; Result&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;~&amp;amp;~A ~A!~%&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;greet&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;def-test&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;greet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;;*STANDARD-INPUT* must contain all the responses at once.&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;*STANDARD-INPUT*&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;make-string-input-stream&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;John~%Hi~%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;*STANDARD-OUTPUT*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;make-string-output-stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;*QUERY-IO*&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;make-two-way-stream&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*STANDARD-INPUT*&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*STANDARD-OUTPUT*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
         &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;setf&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;get-output-stream-string&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*STANDARD-OUTPUT*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;; RESULT =&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;; &quot;Name: Greeting: &lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;; Hi John!&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;; &quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;string-equal&lt;/span&gt;
         &lt;span class=&quot;s&quot;&gt;&quot;Hi John!&quot;&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;nth&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;split-sequence:split-sequence&lt;/span&gt;
                 &lt;span class=&quot;sc&quot;&gt;#\newline&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The example’s method only works for functions that have a fixed request/response
sequence, in other words, the test does not need to change its response
dynamically, all the responses can be prepared before the function is executed.&lt;/p&gt;

&lt;p&gt;The reason for this limitation is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make-string-input-stream&lt;/code&gt; creates
streams with fixed content. Even if the original string changes, the stream
content does not. Without the ability to change the stream content after
creation there is no way to implement dynamic responses to application prompts.&lt;/p&gt;

&lt;p&gt;While it does not seem possible to create interactive output-to-input streams
with Common Lisp primitives, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIOP:RUN-PROGRAM&lt;/code&gt; can provide such
output-to-input streams to external processes. This at least indicates that it
can be done for some particular case. On CCL this is accomplished with file
descriptor streams. I have not investigated the detail of the implementation
nor have I looked how it is done for other implementations, so it may
ultimately be a dead end.&lt;/p&gt;

&lt;p&gt;It appears as if there is no easy and uncomplicated solution for setting up a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*STANDARD-OUTPUT*&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*STANDARD-INPUT*&lt;/code&gt; pipe that will enable test functions
to apply logic in-between responses. If you need such a solution some avenues
to investigate are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Test the application as a standalone external process with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIOP:RUN-PROGRAM&lt;/code&gt;. This definitely works but it is exactly what we did not
want to do.&lt;/li&gt;
  &lt;li&gt;Look for a library that implements some kind of pipe function. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cl-plumbing&lt;/code&gt;
may be an option.&lt;/li&gt;
  &lt;li&gt;Try to implement piping with files or sockets.&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Tue, 07 Jan 2020 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2020/testing-command-line-interfaces/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2020/testing-command-line-interfaces/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
      <item>
        <title>Parameterised decorators for easy-routes</title>
        <description>&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;easy-routes&lt;/code&gt; library makes it easy and fast to define Rails-like HTTP
routes for Hunchentoot. It has the concept of decorators which are functions
that form part of the request handling sequence but not part of the main
request handler. Decorator functions are usually general functions which are
applicable to many request handlers, for example, to set up a database
connection before handling a request.&lt;/p&gt;

&lt;h2 id=&quot;parameterless-decorators&quot;&gt;Parameterless decorators&lt;/h2&gt;

&lt;p&gt;Most decorators are simple functions which do not take any parameters from the
request handler. The DB connection decorator can be defined as below:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;@db&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;postmodern:with-connection&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;*db-spec*&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;funcall&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All decorators take the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next&lt;/code&gt; parameter which is the function that should be
called after this decorator has completed its task. The parameter is supplied
by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;easy-routes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A route that uses a decorator is defined like this:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;defroute&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;api-handler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api&quot;&lt;/span&gt;
                       &lt;span class=&quot;ss&quot;&gt;:method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:get&lt;/span&gt;
                       &lt;span class=&quot;ss&quot;&gt;:decorators&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;@db&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;@auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:decorators&lt;/code&gt; keyword takes a list of decorator names in the order they
need to be executed. In the example above the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@db&lt;/code&gt; function will run, followed
by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@auth&lt;/code&gt; function and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api-handler&lt;/code&gt;. Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@auth&lt;/code&gt; runs after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@db&lt;/code&gt;
it can assume an open database connection. Note that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next&lt;/code&gt; parameter is
not mentioned anywhere.&lt;/p&gt;

&lt;h2 id=&quot;parameterised-decorators&quot;&gt;Parameterised decorators&lt;/h2&gt;

&lt;p&gt;For more complex cases where the decorator needs input from the route handler,
it can be defined with extra parameters like this:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;@check&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;predicate&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;http-error&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;funcall&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;predicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;funcall&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;http-error&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;http-error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next&lt;/code&gt; parameter must be last in the parameter list.&lt;/p&gt;

&lt;p&gt;With a predicate function defined like below,&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;test-p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;a route handler can be defined like this:&lt;/p&gt;

&lt;div class=&quot;language-common_lisp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;defroute&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;api-handler&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api&quot;&lt;/span&gt;
     &lt;span class=&quot;ss&quot;&gt;:method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:get&lt;/span&gt;
     &lt;span class=&quot;ss&quot;&gt;:decorators&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;@db&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;@auth&lt;/span&gt; 
                  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;@check&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;test-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hunchentoot:+http-forbidden+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Decorators with additional parameters are passed as a list with the decorator
name as the first list item followed by the parameters, excluding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;next&lt;/code&gt;. The
list is not quoted.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Dec 2019 00:00:00 +0000</pubDate>
        <link>https://www.darkchestnut.com/2019/easy-routes-parameterised-decorators/</link>
        <guid isPermaLink="true">https://www.darkchestnut.com/2019/easy-routes-parameterised-decorators/</guid>
        
        
        <category>lisp</category>
        
      </item>
    
  </channel>
</rss>
