<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: vindarel</title>
    <description>The latest articles on Forem by vindarel (@vindarel).</description>
    <link>https://forem.com/vindarel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F944315%2F8f355fc5-bda2-42b6-820c-ec559aeb868a.jpeg</url>
      <title>Forem: vindarel</title>
      <link>https://forem.com/vindarel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vindarel"/>
    <language>en</language>
    <item>
      <title>Zed editor plugin for Common Lisp: installation notes</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Mon, 02 Mar 2026 14:35:19 +0000</pubDate>
      <link>https://forem.com/vindarel/zed-editor-plugin-for-common-lisp-installation-notes-4414</link>
      <guid>https://forem.com/vindarel/zed-editor-plugin-for-common-lisp-installation-notes-4414</guid>
      <description>&lt;p&gt;The very new Zed extension lives here: &lt;a href="https://github.com/etyurkin/zed-cl/" rel="noopener noreferrer"&gt;https://github.com/etyurkin/zed-cl/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;As it seems it's a quickly generated AI plugin. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install Zed: &lt;a href="https://zed.dev/" rel="noopener noreferrer"&gt;https://zed.dev/&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;you need a correct GPU driver (for Vulkan). This took me 1GB of space and it required to restart the computer.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;look at issue #1: &lt;a href="https://github.com/etyurkin/zed-cl/issues/1" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://github.com/etyurkin/zed-cl/issues/1" rel="noopener noreferrer"&gt;https://github.com/etyurkin/zed-cl/issues/1&lt;/a&gt; if it's still open, clone the project of the pending PR.&lt;/li&gt;

&lt;li&gt;run &lt;code&gt;make build&lt;/code&gt; and see.&lt;/li&gt;

&lt;li&gt;you need a Rust toolchain (rustc, cargo, rustup)

&lt;ul&gt;
&lt;li&gt;count 1GB of disk space, less if you exclude rust-docs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;you will install tree-sitter-commonlisp on the Lisp and Rust sides, you'll build the LSP server, a Jupyter kernel, a TUI REPL client, the Zed extension in WASM (and a couple hundred crates)

&lt;ul&gt;
&lt;li&gt;this will already take a few minutes of compilation, depending on your machine&lt;/li&gt;
&lt;li&gt;for this step you need the &lt;code&gt;wasm32-wasip1&lt;/code&gt; target: &lt;code&gt;rustup target add wasm32-wasip1&lt;/code&gt;. I stopped here.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;That's all for now :D&lt;/p&gt;

</description>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Common Lisp needs more documentation, tools and ideas (where are we on this?)</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Wed, 17 Dec 2025 16:27:45 +0000</pubDate>
      <link>https://forem.com/vindarel/common-lisp-needs-more-documentation-tools-and-ideas-where-are-we-on-this-io</link>
      <guid>https://forem.com/vindarel/common-lisp-needs-more-documentation-tools-and-ideas-where-are-we-on-this-io</guid>
      <description>&lt;p&gt;This comment is like a mantra on reddit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lisp needs more fresh documentation, more fresh tooling, and more fresh ideas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't disagree, but it's sad to read the same comment on threads 10 years ago or today. Let's put some perspective. Did something change for the better in the last years??&lt;/p&gt;

&lt;p&gt;Let's see what new things we got since 2018 or so that I'm looking around here. At that time, it was so much more difficult to get into CL.&lt;/p&gt;

&lt;p&gt;If you rant, you're allowed to, but please be happy to rant today!&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;common-lisp.net was revamped (it was pushing people off), &lt;/li&gt;
&lt;li&gt;revamped and (way, way, way) better Cookbook, &lt;/li&gt;
&lt;li&gt;CL Community Spec (everybody dreamed about this, it's done), &lt;/li&gt;
&lt;li&gt;Novaspec, &lt;/li&gt;
&lt;li&gt;common-lisp-libraries.readthedocs.io, &lt;/li&gt;
&lt;li&gt;better awesome-cl, at least a list of libraries that makes sense, before we only had Cliki (a mess),&lt;/li&gt;
&lt;li&gt;Qlot for project-local dependencies à la pip/npm/you name it (not that it's better or that Common Lisp need it, you might not need it), &lt;/li&gt;
&lt;li&gt;ocicl (incl. its linter), a new take on package management,&lt;/li&gt;
&lt;li&gt;Ultralisp, a new take on Quicklisp distribution,&lt;/li&gt;
&lt;li&gt;vend, a new take on libraries management,&lt;/li&gt;
&lt;li&gt;mallet, another linter, &lt;/li&gt;
&lt;li&gt;more distribution channels of SBCL, including for Windows, including with system libraries baked in,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the editors front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;newly maintained Atom/Pulsar extension, well developed, featureful,&lt;/li&gt;
&lt;li&gt;rediscovered Eclipse plugin, &lt;/li&gt;
&lt;li&gt;new VSCode extension (or two?) and LSP tooling, might need more love but hey! It's here and working,&lt;/li&gt;
&lt;li&gt;new SLT extension for the Jetbrains IDE, &lt;/li&gt;
&lt;li&gt;new extension for Sublime, &lt;/li&gt;
&lt;li&gt;new one for Geany,&lt;/li&gt;
&lt;li&gt;improved Jupyter kernel and extensions,&lt;/li&gt;
&lt;li&gt;improved cl-repl,&lt;/li&gt;
&lt;li&gt;new icl tool, an again better cl-repl,&lt;/li&gt;
&lt;li&gt;very capable Lem for many languages, with LSP, AI and whatnot,&lt;/li&gt;
&lt;li&gt;maybe new tooling for Vim/Neovim, &lt;/li&gt;
&lt;li&gt;even free extensions for LispWorks, &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and finally:&lt;/p&gt;

&lt;p&gt;dare I cite a good &lt;a href="https://www.udemy.com/course/common-lisp-programming/?couponCode=BUSYCODERS2025" rel="noopener noreferrer"&gt;Udemy course&lt;/a&gt; (there was an old bad one)… not listing libraries (wait, cl-tuition is awesome and fills a gap, wait, Coalton is phenomenal) and not counting other doc or tools attempts and projects waiting for more content or the last touch.&lt;/p&gt;




&lt;p&gt;Let's be more precise when ranting please and let's everybody contribute to those most welcome projects and support their creators!&lt;/p&gt;

&lt;p&gt;Best,&lt;/p&gt;




&lt;p&gt;&lt;em&gt;you'll find all the links somewhere here:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lisp-journey.gitlab.io/blog/these-years-in-common-lisp-2023-2024-in-review/#about-lem-and-rooms-pair-programming-environment" rel="noopener noreferrer"&gt;https://lisp-journey.gitlab.io/blog/these-years-in-common-lisp-2023-2024-in-review/#about-lem-and-rooms-pair-programming-environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodyReichert/awesome-cl/" rel="noopener noreferrer"&gt;https://github.com/CodyReichert/awesome-cl/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lispcookbook.github.io/cl-cookbook/" rel="noopener noreferrer"&gt;https://lispcookbook.github.io/cl-cookbook/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/azzamsa/awesome-cl-software" rel="noopener noreferrer"&gt;https://github.com/azzamsa/awesome-cl-software&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;cover image: Lem editor&lt;/em&gt;&lt;/p&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Common Lisp script: search and transform music files to mp3 with ffmpeg</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Tue, 09 Sep 2025 12:54:47 +0000</pubDate>
      <link>https://forem.com/vindarel/common-lisp-script-search-and-transform-music-files-to-mp3-with-ffmpeg-3phn</link>
      <guid>https://forem.com/vindarel/common-lisp-script-search-and-transform-music-files-to-mp3-with-ffmpeg-3phn</guid>
      <description>&lt;p&gt;We leverage some cool libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ciel &lt;span class="nt"&gt;-s&lt;/span&gt; ffmpeg delux flac

&lt;span class="o"&gt;[&lt;/span&gt;…]

    TITLE           : Cria de Favela
    track           : 12
    TRACKTOTAL      : 12
  Duration: 00:02:51.60, start: 0.000000, bitrate: 954 kb/s
  Stream &lt;span class="c"&gt;#0:0: Audio: flac, 44100 Hz, stereo, s16&lt;/span&gt;
  Stream &lt;span class="c"&gt;#0:1: Video: mjpeg (Progressive), yuvj420p(pc, bt470bg/unknown/unknown), 1280x1280 [SAR 120:120 DAR 1:1], 90k tbr, 90k tbn, 90k tbc (attached pic)&lt;/span&gt;
    Metadata:
      comment         : Cover &lt;span class="o"&gt;(&lt;/span&gt;front&lt;span class="o"&gt;)&lt;/span&gt;
      title           : D:&lt;span class="se"&gt;\M&lt;/span&gt;úsica em alta resolução&lt;span class="se"&gt;\A&lt;/span&gt;lbum&lt;span class="se"&gt;\C&lt;/span&gt;riolo&lt;span class="se"&gt;\[&lt;/span&gt;2017] Espiral de Ilusão - Deluxe Edition&lt;span class="se"&gt;\E&lt;/span&gt;spiral de Ilusão - Deluxe Edition.jpg
Stream mapping:
  Stream &lt;span class="c"&gt;#0:1 -&amp;gt; #0:0 (mjpeg (native) -&amp;gt; png (native))&lt;/span&gt;
  Stream &lt;span class="c"&gt;#0:0 -&amp;gt; #0:1 (flac (native) -&amp;gt; mp3 (libmp3lame))&lt;/span&gt;
Press &lt;span class="o"&gt;[&lt;/span&gt;q] to stop, &lt;span class="o"&gt;[&lt;/span&gt;?] &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;help

&lt;/span&gt;&lt;span class="k"&gt;done for &lt;/span&gt;files: 
&lt;span class="s2"&gt;"/home/vince/zique/[2017] Espiral de Ilusão - Deluxe Edition/01 - Criolo - Lá Vem Você.flac"&lt;/span&gt;
&lt;span class="s2"&gt;"/home/vince/zique/[2017] Espiral de Ilusão - Deluxe Edition/02 - Criolo - Dilúvio de Solidão.flac"&lt;/span&gt;
&lt;span class="s2"&gt;"/home/vince/zique/[2017] Espiral de Ilusão - Deluxe Edition/03 - Criolo - Menino Mimado.flac"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy!&lt;/p&gt;

&lt;p&gt;We find files with &lt;a href="https://github.com/lisp-maintainers/file-finder/" rel="noopener noreferrer"&gt;file-finder&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:finder*&lt;/span&gt;
   &lt;span class="ss"&gt;:root&lt;/span&gt; &lt;span class="nv"&gt;root&lt;/span&gt;
   &lt;span class="c1"&gt;;; This "and"s the params:&lt;/span&gt;
   &lt;span class="ss"&gt;:predicates&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;finder:every-path~&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="c1"&gt;;; This would do a "or":&lt;/span&gt;
   &lt;span class="c1"&gt;;; :predicates (apply #'finder:path~ params)&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This searches for files recursively on a root directory and it "ands" our list of search terms.&lt;/p&gt;

&lt;p&gt;We could be more precise in asking it to check that "flac" is the file extension, but it's ok for us here.&lt;/p&gt;

&lt;p&gt;This gives us a list of "file-finder:file" objects, actually not lisp pathnames, not strings. To get file names as strings, we use &lt;code&gt;finder:path file&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To get a file name with the mp3 extension, we simply replace in a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;extension&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pathname-type&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;values&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:replace-all&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt; &lt;span class="s"&gt;"mp3"&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, TIL. We have directory names that give the year in between &lt;code&gt;[…]&lt;/code&gt; characters, and &lt;code&gt;uiop:file-exists-p&lt;/code&gt; chokes at it (it returns nil). We escape those characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;escape-file-name&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Escape [ and ] with double \\,

  otherwise uiop:file-exists-p returns NIL for an existing file."&lt;/span&gt;
  &lt;span class="c1"&gt;;; This works on upstream file-finder &amp;lt;2025-09-09&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;;; (when (finder:file? name)&lt;/span&gt;
    &lt;span class="c1"&gt;;; (setf name (finder:path name)))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:replace-using&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"["&lt;/span&gt; &lt;span class="s"&gt;"\\["&lt;/span&gt;
                       &lt;span class="s"&gt;"]"&lt;/span&gt; &lt;span class="s"&gt;"\\]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                     &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(edit) OK, those are wildcard characters. This pattern correctly returns T when a file exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;probe-file&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;make-pathname&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="ss"&gt;:type&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so I might adapt the script.&lt;/p&gt;

&lt;p&gt;And that's pretty it. We run &lt;code&gt;ffmpeg&lt;/code&gt; with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:run-program&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="s"&gt;"ffmpeg"&lt;/span&gt;
                                &lt;span class="s"&gt;"-i"&lt;/span&gt;
                                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:path&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                          &lt;span class="ss"&gt;:output&lt;/span&gt; &lt;span class="ss"&gt;:interactive&lt;/span&gt;
                          &lt;span class="ss"&gt;:error-output&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the full listing.&lt;/p&gt;

&lt;p&gt;Find it also on &lt;a href="https://github.com/ciel-lang/CIEL/discussions/89" rel="noopener noreferrer"&gt;https://github.com/ciel-lang/CIEL/discussions/89&lt;/a&gt; and in CIEL's scripts directory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ciel-lang/CIEL/" rel="noopener noreferrer"&gt;https://github.com/ciel-lang/CIEL/&lt;/a&gt; - a batteries-included Common Lisp. Here we only rely on the built-in file-finder library, and cl-str. And the easy way to run a script from the terminal.&lt;/li&gt;
&lt;/ul&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in-package&lt;/span&gt; &lt;span class="ss"&gt;:ciel-user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; Search files and transform them to mp3 with ffmpeg.&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; Usage:&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; ciel src/scripts/ffmpeg search terms&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; Todo:&lt;/span&gt;
&lt;span class="c1"&gt;;;; - choose search directories (do we even search on the current directory?)&lt;/span&gt;
&lt;span class="c1"&gt;;;; - choose output format, etc.&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; TIL: we need to escape file names if they contain characters such as [ ].&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defparameter&lt;/span&gt; &lt;span class="vg"&gt;*directories*&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/Music/"&lt;/span&gt; &lt;span class="s"&gt;"~/Downloads/"&lt;/span&gt; &lt;span class="s"&gt;"~/zique/"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defvar&lt;/span&gt; &lt;span class="vg"&gt;*music-type-extensions*&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"aac"&lt;/span&gt; &lt;span class="s"&gt;"ac3"&lt;/span&gt; &lt;span class="s"&gt;"aiff"&lt;/span&gt; &lt;span class="s"&gt;"amr"&lt;/span&gt; &lt;span class="s"&gt;"ape"&lt;/span&gt; &lt;span class="s"&gt;"dts"&lt;/span&gt; &lt;span class="s"&gt;"f4a"&lt;/span&gt; &lt;span class="s"&gt;"f4b"&lt;/span&gt; &lt;span class="s"&gt;"flac"&lt;/span&gt; &lt;span class="s"&gt;"gsm"&lt;/span&gt;
         &lt;span class="s"&gt;"m3u"&lt;/span&gt; &lt;span class="s"&gt;"m4a"&lt;/span&gt; &lt;span class="s"&gt;"midi"&lt;/span&gt; &lt;span class="s"&gt;"mlp"&lt;/span&gt; &lt;span class="s"&gt;"mka"&lt;/span&gt; &lt;span class="s"&gt;"mp2"&lt;/span&gt; &lt;span class="s"&gt;"mp3"&lt;/span&gt; &lt;span class="s"&gt;"oga"&lt;/span&gt; &lt;span class="s"&gt;"ogg"&lt;/span&gt; &lt;span class="s"&gt;"opus"&lt;/span&gt; &lt;span class="s"&gt;"pva"&lt;/span&gt;
                                  &lt;span class="s"&gt;"ra"&lt;/span&gt; &lt;span class="s"&gt;"ram"&lt;/span&gt; &lt;span class="s"&gt;"raw"&lt;/span&gt; &lt;span class="s"&gt;"rf64"&lt;/span&gt; &lt;span class="s"&gt;"spx"&lt;/span&gt; &lt;span class="s"&gt;"tta"&lt;/span&gt; &lt;span class="s"&gt;"wav"&lt;/span&gt; &lt;span class="s"&gt;"wavpack"&lt;/span&gt; &lt;span class="s"&gt;"wma"&lt;/span&gt; &lt;span class="s"&gt;"wv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"A bunch of audio extensions. Should be supported by ffmpeg. Thanks ready-player.el."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;music-file-p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Return non-NIL if this file (pathname) has a music file extension as of *music-type-extensions*."&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;find&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pathname-type&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="vg"&gt;*music-type-extensions*&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nb"&gt;equalp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;find-on-directory&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Search on default directories.

  PARAMS (list)"&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:finder*&lt;/span&gt;
   &lt;span class="ss"&gt;:root&lt;/span&gt; &lt;span class="nv"&gt;root&lt;/span&gt;
   &lt;span class="c1"&gt;;; This "and"s the params:&lt;/span&gt;
   &lt;span class="ss"&gt;:predicates&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;finder:every-path~&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="c1"&gt;;; This would do a "or":&lt;/span&gt;
   &lt;span class="c1"&gt;;; :predicates (apply #'finder:path~ params)&lt;/span&gt;
   &lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;find-files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;&amp;amp;optional&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Find files matching PARAMS (a list of strings) on the default directories.

  PARAMS is an 'OR'. I would prefer to 'and' the matches actually (see find-on-directory)."&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unless&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="vg"&gt;*error-output*&lt;/span&gt; &lt;span class="s"&gt;"No search terms supplied.~&amp;amp;Usage: finder.lisp search terms.~&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;return-from&lt;/span&gt; &lt;span class="nv"&gt;find-files&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;str:*ignore-case*&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ensure-list&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;flatten&lt;/span&gt;
     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;root&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="vg"&gt;*directories*&lt;/span&gt;
           &lt;span class="nv"&gt;collect&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;find-on-directory&lt;/span&gt; &lt;span class="nv"&gt;root&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;pprint-for-shell&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Pretty-print this list of files (with full path), one per line."&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mapcar&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="s"&gt;"~s~&amp;amp;"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:path&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
          &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;terpri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;change-extension&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:file?&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setf&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:path&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;extension&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pathname-type&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;values&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:replace-all&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt; &lt;span class="s"&gt;"mp3"&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;

&lt;span class="c1"&gt;;; warn!&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;escape-file-name&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Escape [ and ] with double \\,

  otherwise uiop:file-exists-p returns NIL for an existing file."&lt;/span&gt;
  &lt;span class="c1"&gt;;; This works on upstream file-finder &amp;lt;2025-09-09&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;;; (when (finder:file? name)&lt;/span&gt;
    &lt;span class="c1"&gt;;; (setf name (finder:path name)))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:replace-using&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"["&lt;/span&gt; &lt;span class="s"&gt;"\\["&lt;/span&gt;
                       &lt;span class="s"&gt;"]"&lt;/span&gt; &lt;span class="s"&gt;"\\]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                     &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;run-ffmpeg&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Run ffmpeg on FILE, transform to mp3."&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;change-extension&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:file-exists-p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;escape-file-name&lt;/span&gt; &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;progn&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;mp3 already exists: ~a~&amp;amp;"&lt;/span&gt; &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:run-program&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="s"&gt;"ffmpeg"&lt;/span&gt;
                                &lt;span class="s"&gt;"-i"&lt;/span&gt;
                                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:path&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                          &lt;span class="ss"&gt;:output&lt;/span&gt; &lt;span class="ss"&gt;:interactive&lt;/span&gt;
                          &lt;span class="ss"&gt;:error-output&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;ffmpeg-on-files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Transform files to mp3 with ffmpeg.

  Search on the default directories by AND-ing the search terms."&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;files&lt;/span&gt;
        &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;finder:path&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;music-file-p&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:file-exists-p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;escape-file-name&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
          &lt;span class="nb"&gt;do&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;transforming: ~a~&amp;amp;"&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;run-ffmpeg&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;collect&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="nv"&gt;into&lt;/span&gt; &lt;span class="nv"&gt;processed&lt;/span&gt;
        &lt;span class="nv"&gt;finally&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;~%done for files: ~&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pprint-for-shell&lt;/span&gt; &lt;span class="nv"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="o"&gt;#+&lt;/span&gt;&lt;span class="nv"&gt;ciel&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ffmpeg-on-files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;find-files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rest&lt;/span&gt; &lt;span class="vg"&gt;*script-args*&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>lisp</category>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Common Lisp: read password on a terminal (by hiding its input)</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Thu, 04 Sep 2025 20:53:43 +0000</pubDate>
      <link>https://forem.com/vindarel/common-lisp-read-password-on-a-terminal-by-hiding-its-input-2e3j</link>
      <guid>https://forem.com/vindarel/common-lisp-read-password-on-a-terminal-by-hiding-its-input-2e3j</guid>
      <description>&lt;p&gt;We want to have a prompt asking for a password or other sensitive information:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; enter password:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and hide the characters we type:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; enter password: *****
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A solution: call &lt;code&gt;/bin/stty&lt;/code&gt; and enable/disable its echo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;enable-echo&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:run-program&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/bin/stty"&lt;/span&gt; &lt;span class="s"&gt;"echo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ss"&gt;:input&lt;/span&gt; &lt;span class="ss"&gt;:interactive&lt;/span&gt; &lt;span class="ss"&gt;:output&lt;/span&gt; &lt;span class="ss"&gt;:interactive&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;disable-echo&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:run-program&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/bin/stty"&lt;/span&gt; &lt;span class="s"&gt;"-echo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ss"&gt;:input&lt;/span&gt; &lt;span class="ss"&gt;:interactive&lt;/span&gt; &lt;span class="ss"&gt;:output&lt;/span&gt; &lt;span class="ss"&gt;:interactive&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="c1"&gt;;; or:&lt;/span&gt;
  &lt;span class="c1"&gt;;; (sb-ext:run-program "/bin/stty" '("-echo") :input t :output t))&lt;/span&gt;


&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;force-output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;read-line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;pass-prompt&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="s"&gt;"password: "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;force-output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;disable-echo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;unwind-protect&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;read-line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;enable-echo&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;eval-when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:execute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;pass&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pass-prompt&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;I still know the pass was ~s~&amp;amp;"&lt;/span&gt; &lt;span class="nv"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this: &lt;code&gt;sbcl --load read-password.lisp&lt;/code&gt;, and you get the above prompt.&lt;/p&gt;

&lt;p&gt;Other libraries that help hiding sensitive information on other contexts (like, in the REPL output) are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rotatef/secret-values" rel="noopener noreferrer"&gt;https://github.com/rotatef/secret-values&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/atgreen/privacy-output-stream" rel="noopener noreferrer"&gt;https://github.com/atgreen/privacy-output-stream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used them and can recommend them.&lt;/p&gt;

&lt;p&gt;Other libraries are to be found on awesome-cl.&lt;/p&gt;

&lt;p&gt;That's all!&lt;/p&gt;




&lt;p&gt;Lisp?!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hell yes. &lt;a href="https://lisp-journey.gitlab.io/pythonvslisp/" rel="noopener noreferrer"&gt;https://lisp-journey.gitlab.io/pythonvslisp/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodyReichert/awesome-cl/" rel="noopener noreferrer"&gt;https://github.com/CodyReichert/awesome-cl/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/azzamsa/awesome-lisp-companies/" rel="noopener noreferrer"&gt;https://github.com/azzamsa/awesome-lisp-companies/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;debug and resume a program from where it left, don't restart anything: &lt;a href="https://www.youtube.com/watch?v=jBBS4FeY7XM" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=jBBS4FeY7XM&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vindarel/common-lisp-course-in-videos/" rel="noopener noreferrer"&gt;learn Common Lisp efficiently&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Common Lisp: a command-line interactive terminal application in 2 lines</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Sun, 27 Jul 2025 14:11:21 +0000</pubDate>
      <link>https://forem.com/vindarel/common-lisp-a-command-line-interactive-terminal-application-in-2-lines-2gnb</link>
      <guid>https://forem.com/vindarel/common-lisp-a-command-line-interactive-terminal-application-in-2-lines-2gnb</guid>
      <description>&lt;p&gt;The quicker to a MVP the better, right?&lt;/p&gt;

&lt;p&gt;Suppose you have a working Lisp function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;play&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;optional&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;player&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ensure-player&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="s"&gt;"Play this file or directory in the background.

  Return the PLAYER instance.

  See also:

    TOGGLE-PLAY/PAUSE, STOP, QUIT, PLAYLIST-NEXT/PREVIOUS."&lt;/span&gt;

  &lt;span class="c1"&gt;;; Play!&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;with-accessors&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt; &lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nv"&gt;player&lt;/span&gt;
    &lt;span class="c1"&gt;;; but stop the current process if it's alive.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:process-alive-p&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stop&lt;/span&gt; &lt;span class="nv"&gt;player&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;;; If we get a pathname, get the file name as string.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setf&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:launch-program&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mpv-command&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:native-namestring&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;
  &lt;span class="nv"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works in the Common Lisp REPL. It works in the Lem editor. How can we make it work (fast) on the terminal?&lt;/p&gt;

&lt;p&gt;Quickload the &lt;a href="https://github.com/vindarel/replic" rel="noopener noreferrer"&gt;replic&lt;/a&gt; library (based on &lt;a href="https://github.com/vindarel/cl-readline/" rel="noopener noreferrer"&gt;cl-readline&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Create a file &lt;code&gt;run.lisp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; run.lisp&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt; &lt;span class="s"&gt;"media-player.lisp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;;; your library&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in-package&lt;/span&gt; &lt;span class="ss"&gt;:media-player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; TODO: yes, next step is to set-up a proper.asd system definition.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;eval-when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:compile-toplevel&lt;/span&gt; &lt;span class="ss"&gt;:load-toplevel&lt;/span&gt; &lt;span class="ss"&gt;:execute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;;; Load helper libraries (you need quicklisp).&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ql:quickload&lt;/span&gt; &lt;span class="s"&gt;"replic"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; The exported functions of :media-player will be available&lt;/span&gt;
&lt;span class="c1"&gt;;; in the readline prompt.&lt;/span&gt;
&lt;span class="c1"&gt;;; TAB-completion works to write them.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;replic.completion:functions-to-commands&lt;/span&gt; &lt;span class="ss"&gt;:media-player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; Start the readline command loop:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;replic:repl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and that's it: 2 lines.&lt;/p&gt;

&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sbcl --load run.lisp
&amp;gt; play "~/zique/2000\ -\ Jorge\ Aragão\ Ao\ Vivo\ 2/"
&amp;gt; toggle-pause 
&amp;gt; toggle-pause 
&amp;gt; stop
&amp;gt; 
Do you want to quit ? [Y]/n : 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(it's even easier to run a file on the terminal, with batteries included, with &lt;a href="https://github.com/ciel-lang/CIEL/" rel="noopener noreferrer"&gt;CIEL&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TAB-completion for the player commands (play, stop, toggle-pause…)&lt;/li&gt;
&lt;li&gt;dialog confirmation for quit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We could do more, like specifying what to TAB-complete for each different command, but that's our MVP, we can now have fun with the application on the terminal. For example (see replic's README), if we had a &lt;code&gt;hello &amp;lt;name&amp;gt;&lt;/code&gt; command, we'd register every name, and the &lt;code&gt;goodby &amp;lt;name&amp;gt;&lt;/code&gt; command would get TAB-completion of names. Other commands are unaffected. &lt;/p&gt;

&lt;p&gt;How interesting is that to you?&lt;/p&gt;

&lt;p&gt;What replic saved you is: reading cl-readline's documentation, writing readline boilerplate to start the command loop, writing boilerplate to say what to TAB-complete. You can now easily use your  Lisp program from outside of the editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  This was for the Lem editor
&lt;/h2&gt;

&lt;p&gt;The Lem editor is really good. I assembled this media player (all the heavy lifting is done by mpv) to see how it fits in Lem. &lt;/p&gt;

&lt;p&gt;Find the code here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vindarel/lem-media-player" rel="noopener noreferrer"&gt;https://github.com/vindarel/lem-media-player&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fvindarel%2Flem-media-player%2Fraw%2Fmaster%2Fgui-controls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fvindarel%2Flem-media-player%2Fraw%2Fmaster%2Fgui-controls.png" title="Right click menu in Lem to read media files." width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Send emails with Common Lisp (SMTP, Gmail, Sendgrid, Mailgun…)</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Tue, 27 May 2025 18:58:20 +0000</pubDate>
      <link>https://forem.com/vindarel/send-emails-with-common-lisp-smtp-gmail-sendgrid-mailgun-4aac</link>
      <guid>https://forem.com/vindarel/send-emails-with-common-lisp-smtp-gmail-sendgrid-mailgun-4aac</guid>
      <description>&lt;p&gt;Today I want to give an usage example of &lt;a href="https://gitlab.common-lisp.net/cl-smtp/cl-smtp" rel="noopener noreferrer"&gt;cl-smtp&lt;/a&gt;. It's a good, maintained library, with a good README but no proper documentation, nor a ready-to-copy-paste usage example. &lt;/p&gt;

&lt;h2&gt;
  
  
  cl-smtp
&lt;/h2&gt;

&lt;p&gt;Here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cl-smtp:send-email&lt;/span&gt; &lt;span class="s"&gt;"mail.server.org"&lt;/span&gt; &lt;span class="s"&gt;"from@mail.org"&lt;/span&gt; &lt;span class="s"&gt;"to@mailz.org"&lt;/span&gt; &lt;span class="s"&gt;"subject"&lt;/span&gt; &lt;span class="s"&gt;"just a test message from Emacs/Slime."&lt;/span&gt;
  &lt;span class="ss"&gt;:port&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt; &lt;span class="ss"&gt;:ssl&lt;/span&gt; &lt;span class="ss"&gt;:starttls&lt;/span&gt;
  &lt;span class="ss"&gt;:authentication&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="s"&gt;"from@mail.org"&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; =&amp;gt; ("2.0.0 Bye")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda list is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cl-smtp:send-email&lt;/span&gt; &lt;span class="nv"&gt;host&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;subject&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="ss"&gt;:ssl&lt;/span&gt; &lt;span class="nv"&gt;ssl&lt;/span&gt;
    &lt;span class="ss"&gt;:port&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;
    &lt;span class="ss"&gt;:cc&lt;/span&gt; &lt;span class="nv"&gt;cc&lt;/span&gt;
    &lt;span class="ss"&gt;:bcc&lt;/span&gt; &lt;span class="nv"&gt;bcc&lt;/span&gt;
    &lt;span class="ss"&gt;:reply-to&lt;/span&gt; &lt;span class="nv"&gt;reply-to&lt;/span&gt;
    &lt;span class="ss"&gt;:extra-headers&lt;/span&gt; &lt;span class="nv"&gt;extra-headers&lt;/span&gt;
    &lt;span class="ss"&gt;:html-message&lt;/span&gt; &lt;span class="nv"&gt;html-message&lt;/span&gt;
    &lt;span class="ss"&gt;:display-name&lt;/span&gt; &lt;span class="nv"&gt;display-name&lt;/span&gt;
    &lt;span class="ss"&gt;:authentication&lt;/span&gt; &lt;span class="nv"&gt;authentication&lt;/span&gt;
    &lt;span class="ss"&gt;:attachments&lt;/span&gt; &lt;span class="nv"&gt;attachments&lt;/span&gt;
    &lt;span class="ss"&gt;:buffer-size&lt;/span&gt; &lt;span class="nv"&gt;buffer-size&lt;/span&gt;
    &lt;span class="ss"&gt;:envelope-sender&lt;/span&gt; &lt;span class="nv"&gt;envelope-sender&lt;/span&gt;
    &lt;span class="ss"&gt;:external-format&lt;/span&gt; &lt;span class="nv"&gt;external-format&lt;/span&gt;
    &lt;span class="ss"&gt;:local-hostname&lt;/span&gt; &lt;span class="nv"&gt;local-hostname&lt;/span&gt;
    &lt;span class="ss"&gt;:message-id&lt;/span&gt; &lt;span class="nv"&gt;message-id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code snippet allows to send an email from Common Lisp with an existing email account: be it Gmail or any other regular email provider, as long as you know its SMTP address or IP, and your account ID and password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postmaster
&lt;/h2&gt;

&lt;p&gt;Eudoxia's &lt;a href="https://github.com/eudoxia0/postmaster/" rel="noopener noreferrer"&gt;postmaster&lt;/a&gt; is a wrapper around cl-smtp and &lt;a href="https://github.com/40ants/mel-base" rel="noopener noreferrer"&gt;mel-base&lt;/a&gt;. It adds object-orientation to create and use servers as first-class objects, as well as some defaults.&lt;/p&gt;

&lt;p&gt;It uses cl-smtp to send emails, and mel-base to manage pop3/IMAP folders.&lt;/p&gt;

&lt;p&gt;The repository is now read-only, let's maintain it together on &lt;a href="https://github.com/lisp-maintainers/" rel="noopener noreferrer"&gt;https://github.com/lisp-maintainers/&lt;/a&gt; if you are so inclined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sendgrid, Mailgun
&lt;/h2&gt;

&lt;p&gt;If you want to rely on a third-party email provider, you can create an account there and these libraries have you covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/vindarel/cl-sendgrid" rel="noopener noreferrer"&gt;sendgrid&lt;/a&gt; - send emails with Sendgrid's API. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/40ants/mailgun" rel="noopener noreferrer"&gt;mailgun&lt;/a&gt; - A thin wrapper to post HTML emails through mailgun.com.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reading mail: trivial-imap
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/40ants/trivial-imap" rel="noopener noreferrer"&gt;trivial-imap&lt;/a&gt; wants to make mel-base or post-office (a fork of Franz's cl-imap) easier to use. I didn't try any of them.&lt;/p&gt;

&lt;p&gt;It even has the possibility to search for emails. &lt;/p&gt;

&lt;p&gt;Sending (and possibility reading) emails in Common Lisp isn't trendy but just works© ;)&lt;/p&gt;




&lt;p&gt;for any news please check the list of &lt;a href="https://github.com/CodyReichert/awesome-cl/" rel="noopener noreferrer"&gt;https://github.com/CodyReichert/awesome-cl/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>lisp</category>
    </item>
    <item>
      <title>Read CSV files in Common Lisp (cl-csv, data-table)</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Tue, 06 May 2025 16:52:02 +0000</pubDate>
      <link>https://forem.com/vindarel/read-csv-files-in-common-lisp-cl-csv-data-table-3c9n</link>
      <guid>https://forem.com/vindarel/read-csv-files-in-common-lisp-cl-csv-data-table-3c9n</guid>
      <description>&lt;p&gt;I just helped friends for some CSV manipulation and did it for the first time from CL, here's a quick usage overview.&lt;/p&gt;

&lt;p&gt;If you know a better© way please comment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EDIT&lt;/strong&gt;: &lt;a href="https://lisp-stat.dev/docs/manuals/data-frame/#access-data" rel="noopener noreferrer"&gt;lisp-stat's data-frame&lt;/a&gt; is pretty awesome. &lt;/p&gt;

&lt;p&gt;I added the data-table utility in CIEL: &lt;a href="http://ciel-lang.org/#/libraries?id=csv" rel="noopener noreferrer"&gt;http://ciel-lang.org/#/libraries?id=csv&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  cl-csv and a table-like data structure
&lt;/h2&gt;

&lt;p&gt;We'll use &lt;a href="https://github.com/AccelerationNet/cl-csv" rel="noopener noreferrer"&gt;cl-csv&lt;/a&gt;, &lt;a href="https://github.com/AccelerationNet/data-table/" rel="noopener noreferrer"&gt;data-table&lt;/a&gt; and &lt;code&gt;cl-csv-data-tables&lt;/code&gt; (a system defined in cl-csv).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cl-csv&lt;/code&gt; allows to read and write a CSV file, string or stream.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data-table&lt;/code&gt; allows to work with a "table-like" data-structure where&lt;br&gt;
you can access columns by name (instead of only by index), coerce&lt;br&gt;
column types, and more.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cl-csv-data-tables&lt;/code&gt; brings a couple helpers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARN&lt;/strong&gt;: below, &lt;code&gt;csv&lt;/code&gt; is a nickname to &lt;code&gt;ciel-csv&lt;/code&gt; which exports symbols from both cl-csv and data-table.&lt;/p&gt;

&lt;p&gt;Here are the first cl-csv examples:&lt;/p&gt;

&lt;p&gt;Read a file into a list of lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;csv:read-csv&lt;/span&gt; &lt;span class="ss"&gt;#P"file.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;;; &amp;lt;--- note the #P&lt;/span&gt;
&lt;span class="nv"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; read csv from a string (streams are also supported)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;csv:read-csv&lt;/span&gt; &lt;span class="s"&gt;"1,2,3
4,5,6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read a file that's TAB delimited:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;csv:read-csv&lt;/span&gt; &lt;span class="ss"&gt;#P"file.tab"&lt;/span&gt; &lt;span class="ss"&gt;:separator&lt;/span&gt; &lt;span class="sc"&gt;#\Tab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;loop over a CSV for side effects with &lt;code&gt;do-csv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;sum&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;csv:do-csv&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;#P"file.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;incf&lt;/span&gt; &lt;span class="nv"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parse-integer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nth&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="nv"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use &lt;code&gt;map-fn&lt;/code&gt; to do something at each row.&lt;/p&gt;

&lt;p&gt;Below we read a file and return a list of objects created from each row. We could create instances of our own objects like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cl-csv:read-csv&lt;/span&gt; &lt;span class="ss"&gt;#P"file.csv"&lt;/span&gt;
                 &lt;span class="ss"&gt;:map-fn&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;make-instance&lt;/span&gt; &lt;span class="ss"&gt;'object&lt;/span&gt;
                                            &lt;span class="ss"&gt;:foo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nth&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                            &lt;span class="ss"&gt;:baz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nth&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;note that we have to access each column by index.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a table, guess column types
&lt;/h2&gt;

&lt;p&gt;Read a CSV, create a data-table object, assume headers are on the&lt;br&gt;
first row (&lt;code&gt;:has-column-names&lt;/code&gt;), guess the column types (&lt;code&gt;:munge-types&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;csv:get-data-table-from-csv&lt;/span&gt; &lt;span class="ss"&gt;#p"file.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;;; &amp;lt;--- still the #P&lt;/span&gt;
&lt;span class="c1"&gt;;; #&amp;lt;DATA-TABLE:DATA-TABLE {10018A9F63}&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;describe&lt;/span&gt; &lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt;&lt;/span&gt;
  &lt;span class="nv"&gt;COLUMN-NAMES&lt;/span&gt;                   &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Date"&lt;/span&gt; &lt;span class="s"&gt;"Type"&lt;/span&gt; &lt;span class="s"&gt;"Quantity"&lt;/span&gt; &lt;span class="s"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;COLUMN-TYPES&lt;/span&gt;                   &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;STRING&lt;/span&gt; &lt;span class="nv"&gt;STRING&lt;/span&gt; &lt;span class="nv"&gt;INTEGER&lt;/span&gt; &lt;span class="nv"&gt;DOUBLE-FLOAT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;ROWS&lt;/span&gt;                           &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"9 jan. 1975"&lt;/span&gt; &lt;span class="s"&gt;"Sell"&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mf"&gt;9.90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is roughly as if you read the CSV file with &lt;code&gt;cl-csv:read-csv&lt;/code&gt;, checked that the first row contains column names, created the data-table object with &lt;code&gt;(make-instance 'data-table:data-table :column-names (first rows) :rows (rest rows)&lt;/code&gt;, and coerced the columns' types with &lt;code&gt;(data-table:coerce-data-table-of-strings-to-types dt)&lt;/code&gt; and &lt;code&gt;(data-table::ensure-column-data-types dt)&lt;/code&gt; (unexported function).&lt;/p&gt;

&lt;h2&gt;
  
  
  Access rows and columns
&lt;/h2&gt;

&lt;p&gt;do that with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;csv:rows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;csv:data-table-value dt &amp;amp;key row row-idx col-name col-idx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and more&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Write to file (or streams)
&lt;/h3&gt;

&lt;p&gt;Write the data-table to a file with &lt;code&gt;data-table-to-csv dt &amp;amp;optional stream)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world example
&lt;/h2&gt;

&lt;p&gt;That's my CIEL script that I did to help friends of a non-profit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;;; Run this script on many CSV files:&lt;/span&gt;
&lt;span class="c1"&gt;;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;;; $ ciel sumuputils.lisp Rapports*&lt;/span&gt;
&lt;span class="c1"&gt;;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in-package&lt;/span&gt; &lt;span class="ss"&gt;:ciel-user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; Needs the latest CIEL, or this library.&lt;/span&gt;
&lt;span class="c1"&gt;;; (ql:quickload "data-table"  :silent t)&lt;/span&gt;

&lt;span class="c1"&gt;;;; Download the CSV file from Sum Up&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defvar&lt;/span&gt; &lt;span class="vg"&gt;*file*&lt;/span&gt;  &lt;span class="ss"&gt;#p"/path/to/Rapport-ventes-2024-11-01_2024-11-30.csv"&lt;/span&gt;
  &lt;span class="s"&gt;"Only for testing."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defvar&lt;/span&gt; &lt;span class="vg"&gt;*dt*&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="s"&gt;"devel only"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;parse-csv&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"Parse CSV, return a data-table object with column names and rows.

  file: a pathname (not just a string)."&lt;/span&gt;
  &lt;span class="c1"&gt;;; This takes headers as the first row&lt;/span&gt;
  &lt;span class="c1"&gt;;; and guesses the columns' types (string, int, float).&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;csv:get-data-table-from-csv&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pathname&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;get-all-days&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;remove-duplicates&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:rows&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;date/time&lt;/span&gt; &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:unwords&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;split-sequence&lt;/span&gt; &lt;span class="sc"&gt;#\Space&lt;/span&gt; &lt;span class="nv"&gt;date/time&lt;/span&gt; &lt;span class="ss"&gt;:count&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="nv"&gt;collect&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nb"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; (get-all-days (parse-csv *FILE*))&lt;/span&gt;
&lt;span class="c1"&gt;;; ("1 nov. 2024" "2 nov. 2024" "5 nov. 2024" "7 nov. 2024" "8 nov. 2024" …)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;get-all-conso-types&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sort&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;remove-duplicates&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:rows&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:non-blank-string-p&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nv"&gt;collect&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nb"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nb"&gt;string&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;get-all-offert-types&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;types&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:containsp&lt;/span&gt; &lt;span class="s"&gt;"offert"&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nv"&gt;types&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;#+&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get-all-offert-types&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get-all-conso-types&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;parse-csv&lt;/span&gt; &lt;span class="vg"&gt;*FILE*&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;rows-offerts-for-day&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="s"&gt;"day: string, like '23 nov"&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:rows&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:containsp&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:containsp&lt;/span&gt; &lt;span class="s"&gt;"offert"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
          &lt;span class="nv"&gt;collect&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;sum-quantities-offerts-for-day&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;;; optionnel: pour chq jour, combien de tartines, alcool, soft… d'offerts? (en nombre)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rows-offerts-for-day&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt; &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Quantité"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt;
          &lt;span class="nv"&gt;sum&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; (sum-quantities-offerts-for-day "23 nov" *dt*)&lt;/span&gt;
&lt;span class="c1"&gt;;; 15&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;sum-quantities-offerts-for-day/by-type&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="nv"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;;; optionnel: pour chq jour, combien de tartines, alcool, soft… d'offerts? (en nombre)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rows-offerts-for-day&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt; &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Quantité"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt;
                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;str:containsp&lt;/span&gt; &lt;span class="nv"&gt;desc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
          &lt;span class="nv"&gt;sum&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;report-sum-quantities-offerts-for-day/by-type&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;aux&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setf&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;parse-csv&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get-all-days&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;do&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;~%Nombre de consos offertes le ~a~&amp;amp;~%"&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;desc&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get-all-offert-types&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get-all-conso-types&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                 &lt;span class="nb"&gt;do&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~a: ~a~&amp;amp;"&lt;/span&gt; &lt;span class="nv"&gt;desc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sum-quantities-offerts-for-day/by-type&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="nv"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;


&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;sum-total-offerts-for-day&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;;; le + important&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rows-offerts-for-day&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt; &lt;span class="nb"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data-table:data-table-value&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="ss"&gt;:row&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="ss"&gt;:col-name&lt;/span&gt; &lt;span class="s"&gt;"Prix (TTC)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt;
          &lt;span class="nv"&gt;sum&lt;/span&gt; &lt;span class="nv"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; (sum-total-offerts-for-day "23 nov" *dt*)&lt;/span&gt;
&lt;span class="c1"&gt;;; 49.0d0&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;report-totals-offert-for-days&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;parse-csv&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;~%Total TTC des offerts~&amp;amp;~%"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;(somme des colonnes Prix (TTC) pour toutes les lignes du jour comportant 'conso offerte')~&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get-all-days&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="nb"&gt;do&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~20a: ~4f~&amp;amp;"&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sum-total-offerts-for-day&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;report-number-offert-for-days&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;dt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;parse-csv&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;Nombre des consos offertes par jour~&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~&amp;amp;(somme des colonnes 'Quantité' pour toutes les lignes du jour comportant 'conso offerte')~&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;get-all-days&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="nb"&gt;do&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;stream&lt;/span&gt; &lt;span class="s"&gt;"~20a: ~4a~&amp;amp;"&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sum-quantities-offerts-for-day&lt;/span&gt; &lt;span class="nv"&gt;day&lt;/span&gt; &lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

&lt;span class="o"&gt;#+&lt;/span&gt;&lt;span class="nv"&gt;ciel&lt;/span&gt;

&lt;span class="o"&gt;#+&lt;/span&gt;&lt;span class="nv"&gt;ciel&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mapcar&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nb"&gt;pathname&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rest&lt;/span&gt; &lt;span class="nv"&gt;ciel-user:*script-args*&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mapcar&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;report-totals-offert-for-days&lt;/span&gt; &lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mapcar&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;report-sum-quantities-offerts-for-day/by-type&lt;/span&gt; &lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from the command line:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ciel sumuputils.lisp Rapport*csv &amp;gt; rapport.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Show data in table
&lt;/h3&gt;

&lt;p&gt;This is our CSV, by the way:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date,Type,Réf. transaction,Moyen de paiement,Quantité,Description,Devise,Prix avant réduction,Réduction,Prix (TTC),Prix (HT),TVA,Taux de TVA,Compte
1 nov. 2024 22:09,Vente,T3YKZYZANT,Mastercard - Crédit,1,Montant personnalisé,EUR,3.00,0.00,3.00,3.00,0.00,,us@gmail.com
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can we display it nicely in a table? We'll use our resurrected &lt;a href="https://github.com/vindarel/cl-ansi-term" rel="noopener noreferrer"&gt;cl-ansi-term&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;We'll use the "raw" CSV data, where the headers are on the first line, instead of the data-value object.&lt;/p&gt;

&lt;p&gt;That way we just have to pass it to &lt;code&gt;term:table *csv*&lt;/code&gt;, and we choose the columns to display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setf&lt;/span&gt; &lt;span class="vg"&gt;*rawcsv*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;csv:read-csv&lt;/span&gt; &lt;span class="ss"&gt;#p"test.csv"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;term:table&lt;/span&gt; &lt;span class="vg"&gt;*RAWCSV*&lt;/span&gt; &lt;span class="ss"&gt;:keys&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Date"&lt;/span&gt; &lt;span class="s"&gt;"Quantité"&lt;/span&gt; &lt;span class="s"&gt;"Prix (HT)"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------+--------+---------+
|Date             |Quantité|Prix (HT)|
+-----------------+--------+---------+
|1 nov. 2024 22:09|1       |3.00     |
+-----------------+--------+---------+
|2 nov. 2024 19:10|1       |6.00     |
+-----------------+--------+---------+
|2 nov. 2024 20:25|1       |6.00     |
+-----------------+--------+---------+
|2 nov. 2024 20:44|1       |10.00    |
+-----------------+--------+---------+
|2 nov. 2024 21:05|1       |6.00     |
+-----------------+--------+---------+
|2 nov. 2024 21:13|1       |3.00     |
+-----------------+--------+---------+
|2 nov. 2024 21:45|1       |7.00     |
+-----------------+--------+---------+
|2 nov. 2024 21:49|1       |6.00     |
+-----------------+--------+---------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lisp-stat's data-frame
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://lisp-stat.dev/docs/manuals/data-frame/" rel="noopener noreferrer"&gt;lisp-stat's data-frame&lt;/a&gt; is the smart way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ql:quickload&lt;/span&gt; &lt;span class="ss"&gt;:lisp-stat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; …&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in-package&lt;/span&gt; &lt;span class="ss"&gt;:ls-user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; Give a name to our dataframe,&lt;/span&gt;
&lt;span class="c1"&gt;;; read CSV.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;defdf&lt;/span&gt; &lt;span class="nv"&gt;sumup&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;read-csv&lt;/span&gt; &lt;span class="ss"&gt;#p"sells.csv"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;;; similar to (defparameter *data-frame* …) but with future goodies.&lt;/span&gt;

&lt;span class="c1"&gt;;; The goodies are that each column is created as a variable for our "sumup" dataframe.&lt;/span&gt;
&lt;span class="c1"&gt;;; Here we get all the "date" columns:&lt;/span&gt;
&lt;span class="nv"&gt;LS-USER&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;sumup:date&lt;/span&gt;
&lt;span class="c1"&gt;;; #("1 nov. 2024 19:36" "1 nov. 2024 19:36" …)&lt;/span&gt;

&lt;span class="c1"&gt;;; We didn't need to LOOP.&lt;/span&gt;

&lt;span class="c1"&gt;;; We can also use &lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;select&lt;/span&gt; &lt;span class="nv"&gt;sumup&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt; &lt;span class="ss"&gt;'date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; We have rows an column manipulation functions.&lt;/span&gt;
&lt;span class="c1"&gt;;; Let's get the data of only 2 columns:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;columns&lt;/span&gt; &lt;span class="nv"&gt;sumup&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt; &lt;span class="nv"&gt;quantit&lt;/span&gt;&lt;span class="err"&gt;é&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; Let's inspect our data:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;head&lt;/span&gt; &lt;span class="nv"&gt;sumup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;;   DATE              TYPE  RÉF--TRANSACTION MOYEN-DE-PAIEMENT  QUANTITÉ DESCRIPTION          DEVISE PRIX-AVANT-RÉDUCTION RÉDUCTION PRIX-(TTC) PRIX-(HT) TVA TAUX-DE-TVA COMPTE                     &lt;/span&gt;
&lt;span class="c1"&gt;;; 0 1 nov. 2024 19:36 Vente TE4PL6DA9X       Mastercard - Débit        1 Montant personnalisé EUR                     9.0       0.0        9.0       9.0 0.0          NA us@gmail.com&lt;/span&gt;

&lt;span class="c1"&gt;;; the data is printed as a nice table, respecting the columns width out of the box.&lt;/span&gt;

&lt;span class="c1"&gt;;; print-data would print all the data.&lt;/span&gt;

&lt;span class="c1"&gt;;; DESCRIBE output is pimped:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;describe&lt;/span&gt; &lt;span class="nv"&gt;sumup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;SUMUP&lt;/span&gt;
  &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;data-frame&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt; &lt;span class="mi"&gt;863&lt;/span&gt; &lt;span class="nv"&gt;observations&lt;/span&gt; &lt;span class="nv"&gt;of&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="nv"&gt;variables&lt;/span&gt;

&lt;span class="nv"&gt;Variable&lt;/span&gt;             &lt;span class="nv"&gt;| Type |&lt;/span&gt; &lt;span class="nv"&gt;Unit&lt;/span&gt; &lt;span class="nv"&gt;| Label      
--------             |&lt;/span&gt; &lt;span class="nv"&gt;----&lt;/span&gt; &lt;span class="nv"&gt;| ---- |&lt;/span&gt; &lt;span class="nv"&gt;-----------&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;                 &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="nv"&gt;| NIL        
TYPE                 |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;        
&lt;span class="nv"&gt;R&lt;/span&gt;&lt;span class="err"&gt;É&lt;/span&gt;&lt;span class="nv"&gt;F--TRANSACTION&lt;/span&gt;     &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="nv"&gt;| NIL        
MOYEN-DE-PAIEMENT    |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;        
&lt;span class="nv"&gt;QUANTIT&lt;/span&gt;&lt;span class="err"&gt;É&lt;/span&gt;             &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="nv"&gt;| NIL        
…

;; We can guess the column types:
(heuristicate-types sumup)

;; Our types are not NIL now:
(describe sumup)
SUMUP
  A data-frame with 863 observations of 14 variables

Variable             |&lt;/span&gt; &lt;span class="nv"&gt;Type&lt;/span&gt;         &lt;span class="nv"&gt;| Unit |&lt;/span&gt; &lt;span class="nv"&gt;Label&lt;/span&gt;      
&lt;span class="nv"&gt;--------&lt;/span&gt;             &lt;span class="nv"&gt;| ----         |&lt;/span&gt; &lt;span class="nv"&gt;----&lt;/span&gt; &lt;span class="nv"&gt;| -----------
DATE                 |&lt;/span&gt; &lt;span class="nv"&gt;STRING&lt;/span&gt;       &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;        
&lt;span class="nv"&gt;TYPE&lt;/span&gt;                 &lt;span class="nv"&gt;| STRING       |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="nv"&gt;| NIL        
RÉF--TRANSACTION     |&lt;/span&gt; &lt;span class="nv"&gt;STRING&lt;/span&gt;       &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;        
&lt;span class="nv"&gt;MOYEN-DE-PAIEMENT&lt;/span&gt;    &lt;span class="nv"&gt;| STRING       |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="nv"&gt;| NIL        
QUANTITÉ             |&lt;/span&gt; &lt;span class="nv"&gt;INTEGER&lt;/span&gt;      &lt;span class="nv"&gt;| NIL  |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;        
&lt;span class="nv"&gt;PRIX-AVANT-R&lt;/span&gt;&lt;span class="err"&gt;É&lt;/span&gt;&lt;span class="nv"&gt;DUCTION&lt;/span&gt; &lt;span class="nv"&gt;| DOUBLE-FLOAT |&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;  &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;NIL&lt;/span&gt;        
&lt;span class="err"&gt;…&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we have rows and colums manipulation functions at our disposal. Much more, without &lt;code&gt;loop&lt;/code&gt;ing.&lt;/p&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;p&gt;discussion: &lt;a href="https://www.reddit.com/r/Common_Lisp/comments/1kht5ht/read_csv_files_in_common_lisp_clcsv_datatable/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/Common_Lisp/comments/1kht5ht/read_csv_files_in_common_lisp_clcsv_datatable/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If all you want is &lt;em&gt;fast&lt;/em&gt; CSV parsing, redditors mentioned that DuckDB through &lt;a href="https://github.com/ak-coram/cl-duckdb" rel="noopener noreferrer"&gt;cl-duckdb&lt;/a&gt; is pretty good.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://lisp-stat.dev/docs/manuals/data-frame/" rel="noopener noreferrer"&gt;lisp-stat's dataframe&lt;/a&gt; can read CSV. It's the smart way.

&lt;ul&gt;
&lt;li&gt;easy overview&lt;/li&gt;
&lt;li&gt;direct access to row and columns manipulation, with no need of loop-ing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/defunkydrummer/auto-text" rel="noopener noreferrer"&gt;auto-text&lt;/a&gt;, automatic detection for text files (encoding, end of line, column width, csv delimiter etc). &lt;a href="https://github.com/t-sin/inquisitor" rel="noopener noreferrer"&gt;inquisitor&lt;/a&gt; for detection of asian and far eastern languages.&lt;/li&gt;

&lt;li&gt;  &lt;a href="https://github.com/sharplispers/clawk" rel="noopener noreferrer"&gt;CLAWK&lt;/a&gt;, an AWK implementation embedded into Common Lisp, to parse files line-by-line.&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;Lisp?!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lispcookbook.github.io/cl-cookbook/" rel="noopener noreferrer"&gt;https://lispcookbook.github.io/cl-cookbook/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lisp-journey.gitlab.io/" rel="noopener noreferrer"&gt;https://lisp-journey.gitlab.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CodyReichert/awesome-cl/" rel="noopener noreferrer"&gt;https://github.com/CodyReichert/awesome-cl/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.udemy.com/course/common-lisp-programming/" rel="noopener noreferrer"&gt;https://www.udemy.com/course/common-lisp-programming/&lt;/a&gt; with this coupon code: BUSYCODERS2025&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Common Lisp: is LispWorks worth it?</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Wed, 12 Feb 2025 16:30:16 +0000</pubDate>
      <link>https://forem.com/vindarel/common-lisp-is-lispworks-worth-it-57i4</link>
      <guid>https://forem.com/vindarel/common-lisp-is-lispworks-worth-it-57i4</guid>
      <description>&lt;p&gt;Let's seek some feedback by people who used it.&lt;/p&gt;

&lt;p&gt;(emphasis mine)&lt;/p&gt;




&lt;p&gt;I own LW for Linux, Mac, and Windows. That's a lot of $$$. But I can build &lt;strong&gt;trimmed down binaries&lt;/strong&gt; for multiple platforms. If there's another platform I need, I can just pay LW, and it'll just work. (I used a beta version of LW for &lt;strong&gt;Android&lt;/strong&gt; for a while, and I was impressed with how &lt;strong&gt;everything just works&lt;/strong&gt;, I chose not to keep paying for it though since Android didn't remain a primary platform I was working on). As a solo dev, it would take me too much time to make my code work on multiple platforms without LW.&lt;/p&gt;

&lt;p&gt;I like the &lt;strong&gt;Java&lt;/strong&gt; support. I can use Java libraries when I need it. Very valuable.&lt;/p&gt;

&lt;p&gt;I love LW's FLI vs something like &lt;strong&gt;CFFI&lt;/strong&gt;. It's just very well thought through, and really well documented.&lt;/p&gt;

&lt;p&gt;Speaking of &lt;strong&gt;documentation&lt;/strong&gt;, the LW documentation is outstanding, and the folk at LW will respond to you explaining implementation details that aren't documented if you need it.&lt;/p&gt;

&lt;p&gt;And it's &lt;strong&gt;rock solid&lt;/strong&gt;. I have my server running for weeks and weeks at a time, with constantly reloading code. I don't think I've ever had a crash in prod except when I ran out of memory on occasion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: not every library is well tested with LW. You'll frequently have to fix libraries, or have your own fork of things.&lt;/p&gt;

&lt;p&gt;I haven't used &lt;strong&gt;CAPI&lt;/strong&gt; [their cross-platform GUI toolkit, based on Gtk on GNU/Linux] much. I don't use the &lt;strong&gt;IDE&lt;/strong&gt;, I use Emacs+sly.&lt;/p&gt;

&lt;p&gt;All that being said, &lt;strong&gt;SBCL&lt;/strong&gt; is still fantastic. If you're building a web app, SBCL would work perfectly well for your use case.&lt;/p&gt;

&lt;p&gt;@tdrhq of ScreenShotBot&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/Common_Lisp/comments/yq2t4r/why_buy_lispworks/ivmoexy/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/Common_Lisp/comments/yq2t4r/why_buy_lispworks/ivmoexy/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;My first Lisp job was building a touch-screen GUI for a law enforcement booking system that ran on an embedded Windows OS. We used LispWorks 5 if I recall correctly. It was stellar and extremely stable. Our attempts at the time to port to an open-source compiler were complete boondoggles for a multitude of reasons.&lt;/p&gt;

&lt;p&gt;Aside from stability, the following stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Their &lt;strong&gt;GUI toolkit&lt;/strong&gt; is great if you're building a commercial application.&lt;/li&gt;
&lt;li&gt;Their &lt;strong&gt;documentation&lt;/strong&gt; is better than any open source Lisp compiler.&lt;/li&gt;
&lt;li&gt;Turn-key ways to build optimized, small, and safer &lt;strong&gt;executables&lt;/strong&gt; and &lt;strong&gt;dynamic libraries&lt;/strong&gt;. I don't think any open source Lisp offers good options to shake out unnecessary functionality from the Lisp image.&lt;/li&gt;
&lt;li&gt;You can pay someone to fix things that don't work as well as you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My big &lt;strong&gt;issues&lt;/strong&gt; with LW:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a great Lisp implementation, but it's just an ok &lt;strong&gt;compiler&lt;/strong&gt;. It takes a ton more work to get performant numerical code compared to SBCL.&lt;/li&gt;
&lt;li&gt;Last I checked, it's $3k per OS per processor. So if you want to provide binaries for Win, Mac (Intel, ARM), Linux, you're going to have to shell out $12,000 or so.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;stylewarning, same reddit link &lt;a href="https://www.reddit.com/r/Common_Lisp/comments/yq2t4r/why_buy_lispworks/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/Common_Lisp/comments/yq2t4r/why_buy_lispworks/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The combination of CAPI, &lt;strong&gt;Common SQL&lt;/strong&gt;, &lt;strong&gt;KnowledgeWorks&lt;/strong&gt; and the &lt;strong&gt;Java interface&lt;/strong&gt; inside a single image is difficult to beat. They’re all very solid and battle-tested. This integration as well as the substantial development velocity that comes from using CL is what finally convinced me to pony up the cash, which I definitely don’t regret. My current project is automated planning and scheduling in healthcare and uses all of these features.&lt;/p&gt;

&lt;p&gt;The IDE tooling is solid and things like &lt;strong&gt;breakpoints&lt;/strong&gt;, the debugger, &lt;strong&gt;stepper&lt;/strong&gt;, tracer and profiler are really useful and ‘just work’ as expected. The editor isn’t quite as strong as Emacs, but can be tweaked to paper over the differences, and having immediate access to the documentation and much deeper integration with the rest of the system far outweighs minor annoyances like trailing white space that can be easily fixed with a pre-commit hook.&lt;/p&gt;

&lt;p&gt;Other, smaller, things that I’ve come to appreciate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;advice facility&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;embedded FLI modules&lt;/li&gt;
&lt;li&gt;dspecs&lt;/li&gt;
&lt;li&gt;action lists&lt;/li&gt;
&lt;li&gt;with-prolog / deflogfun to interface &lt;strong&gt;prolog&lt;/strong&gt; and Lisp&lt;/li&gt;
&lt;li&gt;user defined streams over pointers into foreign memory.&lt;/li&gt;
&lt;li&gt;extremely fast start times in delivered applications&lt;/li&gt;
&lt;li&gt;being able to easily integrate my own extensions into the IDE&lt;/li&gt;
&lt;li&gt;gentle on my laptop battery and no annoying ‘help' compared to products like IntelliJ.&lt;/li&gt;
&lt;li&gt;it’s almost trivial to bang out a prototype or some utility application, complete with a user interface, in an afternoon.

&lt;ul&gt;
&lt;li&gt;Like some others, I don’t tend to use saved sessions all that much. The compiler is fast enough that it isn’t a big problem to compile &amp;amp; load your code at the start of a session, and I find it helps prevent unexpected issues some delivery time.&lt;/li&gt;
&lt;li&gt;Yes, it is expensive compared to SBCL+Emacs or VS Code and whatever iteration of JavaScript we’re up to now. But I think you get what you pay for which is a solid, extensible, general purpose development environment with some fairly high-voltage batteries included.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;same link&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.lispworks.com/" rel="noopener noreferrer"&gt;https://www.lispworks.com/&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Refactoring in Common Lisp (and VS Python)</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Sat, 01 Feb 2025 12:17:32 +0000</pubDate>
      <link>https://forem.com/vindarel/refactoring-in-common-lisp-and-vs-python-4a4j</link>
      <guid>https://forem.com/vindarel/refactoring-in-common-lisp-and-vs-python-4a4j</guid>
      <description>&lt;p&gt;&lt;strong&gt;How is the refactoring story for Common Lisp? Is it lacking? We don't have good IDE refactoring tools, do we?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those are legitimate questions, specially when we are evaluating the language and its ecosystem. It pops up regularly on reddit. It's best if we talk about experience and precise points, so here's mine.&lt;/p&gt;




&lt;p&gt;The biggest Common Lisp project I contributed to is Lem. It is close to 300k lines of Lisp code, it exists since at least 2016.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It was very easy to contribute to&lt;/strong&gt;, and I am not the only newcomer to say this. It is well structured, clear code, almost "boring" code. It doesn't use fancy language extensions and has a few macros.&lt;/p&gt;

&lt;p&gt;I received pull requests on top of my contributions (Lem's legit Git interface). One big PR had me do more manual testing that anticipated, but that wasn't because the code didn't compile or was incorrect (a build error or a failed linter in a CI is spotted quickly), it was because of missing behavior (a missing generic function implementation in a given package), because the PR obviously had not been tested by the author.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's true&lt;/strong&gt; that we don't have fancy refactoring tools in CL like in modern IDEs for other platforms (not that I know of), but we have many tools that make small (and larger) refactoring &lt;strong&gt;easy during development&lt;/strong&gt;. Comparing to what I know, the development experience is miles ahead of Python's. CL makes refactorings easy thanks to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;language features (no moving syntax, multiple return values are NOT returning a list or a tuple, methods, macros…),&lt;/li&gt;
&lt;li&gt;implementation features (all the useful errors and warnings at compile-time, when you press a keyboard shortcut to reload one single function or a file or a module or a project),&lt;/li&gt;
&lt;li&gt;tools (the built-in cross-references: who calls this macro? Who uses that function? etc)&lt;/li&gt;
&lt;li&gt;as well as culture (deprecation warnings staying for years (I saw 12 years))&lt;/li&gt;
&lt;li&gt;and more? (I'm sure we could leverage Comby and such tools for more clever such and replace)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TLDR;&lt;/p&gt;

&lt;p&gt;I want to refactor a Python code base: I sweat, I look for the latest tools (if they are compatible), I augment my test coverage, I triple check and deploy to a partner client and I fix issues in production anyways.&lt;/p&gt;

&lt;p&gt;I want to refactor a Common Lisp project: just do it multiple times a day.&lt;/p&gt;




&lt;p&gt;more feedback here &lt;a href="https://www.reddit.com/r/lisp/comments/1ib08ci/on_refactoring_lisp_pros_and_cons/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/lisp/comments/1ib08ci/on_refactoring_lisp_pros_and_cons/&lt;/a&gt; and in the other linked threads.&lt;/p&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>Common Lisp HTML templates: using Djula in .lisp files</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Thu, 02 Jan 2025 20:54:22 +0000</pubDate>
      <link>https://forem.com/vindarel/common-lisp-html-templates-using-djula-in-lisp-files-4e64</link>
      <guid>https://forem.com/vindarel/common-lisp-html-templates-using-djula-in-lisp-files-4e64</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; How to use Djula templates in-file, without separate file templates.&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; This can make it easier to ship your app as a single binary.&lt;/span&gt;
&lt;span class="c1"&gt;;;; Otherwise, see&lt;/span&gt;
&lt;span class="c1"&gt;;;; https://lisp-journey.gitlab.io/blog/lisp-for-the-web-build-standalone-binaries-foreign-libraries-templates-static-assets/#embed-html-djula-templates-in-your-binary&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;; We use two Djula functions:&lt;/span&gt;
&lt;span class="c1"&gt;;;; - compile-string&lt;/span&gt;
&lt;span class="c1"&gt;;;; - render-template*&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;
&lt;span class="c1"&gt;;;;&lt;/span&gt;

&lt;span class="o"&gt;#+&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ql:quickload&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"djula"&lt;/span&gt; &lt;span class="s"&gt;"pythonic-string-reader"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pythonic-string-reader:enable-pythonic-string-syntax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defparameter&lt;/span&gt; &lt;span class="vg"&gt;*template-hello*&lt;/span&gt; &lt;span class="s"&gt;"""
{% if foo %}
 foo is true
{% else %}
 foo is false
{% endif %}
"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;render&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;rest&lt;/span&gt; &lt;span class="nv"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;
   &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;djula:render-template*&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;djula:compile-string&lt;/span&gt; &lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="no"&gt;nil&lt;/span&gt;
   &lt;span class="c1"&gt;;; key arguments:&lt;/span&gt;
   &lt;span class="nv"&gt;keys&lt;/span&gt;
   &lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="o"&gt;#+&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;render&lt;/span&gt; &lt;span class="vg"&gt;*template-hello*&lt;/span&gt; &lt;span class="ss"&gt;:foo&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; =&amp;gt;&lt;/span&gt;
&lt;span class="s"&gt;" foo is true "&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mmontone.github.io/djula/djula/" rel="noopener noreferrer"&gt;https://mmontone.github.io/djula/djula/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lisp-journey.gitlab.io/blog/lisp-for-the-web-build-standalone-binaries-foreign-libraries-templates-static-assets/" rel="noopener noreferrer"&gt;https://lisp-journey.gitlab.io/blog/lisp-for-the-web-build-standalone-binaries-foreign-libraries-templates-static-assets/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lispcookbook.github.io/cl-cookbook/web.html" rel="noopener noreferrer"&gt;https://lispcookbook.github.io/cl-cookbook/web.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
    </item>
    <item>
      <title>Advent of Code: alexandria's map-permutations was perfect for day 08. Common Lisp tip.</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Mon, 09 Dec 2024 17:57:20 +0000</pubDate>
      <link>https://forem.com/vindarel/advent-of-code-alexandrias-map-permutations-was-perfect-for-day-08-common-lisp-tip-16il</link>
      <guid>https://forem.com/vindarel/advent-of-code-alexandrias-map-permutations-was-perfect-for-day-08-common-lisp-tip-16il</guid>
      <description>&lt;p&gt;As the title says. Did you know about &lt;code&gt;alexandria:map-permutations&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;If not, you could have discovered it with &lt;code&gt;(apropos "permutation")&lt;/code&gt;. Run this on the REPL, it returns you all results it knows of symbols that have it in their name. You can also try &lt;code&gt;apropos-regex&lt;/code&gt; from cl-ppcre. It's best to run them when you have loaded a few utility packages, like Alexandria or Serapeum. I run them when I loaded CIEL, so they are here, plus a few more.&lt;/p&gt;

&lt;p&gt;Look at the Alexandria functions on sequences here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://alexandria.common-lisp.dev/draft/alexandria.html#Sequences" rel="noopener noreferrer"&gt;https://alexandria.common-lisp.dev/draft/alexandria.html#Sequences&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;map-permutations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Calls function with each permutation of length constructable from
the subsequence of sequence delimited by start and end. start
defaults to 0, end to length of the sequence, and length to the
length of the delimited subsequence.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you call it on a large input, it won't return an insanely large list of permutations. We can work on them one after the other. You can also pass &lt;code&gt;:copy nil&lt;/code&gt; to it, in which case "all combinations are eq to each other", so I suppose the function re-uses a structure, and it is advised to &lt;em&gt;not&lt;/em&gt; modify the permutation.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alexandria:map-permutations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;;; or just&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alexandria:map-permutations&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alexandria:map-permutations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ask for permutations of length 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alexandria:map-permutations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ss"&gt;:length&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also &lt;code&gt;map-combinations&lt;/code&gt;, this one considers &lt;code&gt;(a b)&lt;/code&gt; and &lt;code&gt;(b a)&lt;/code&gt; to be the same combination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alexandria:map-combinations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ss"&gt;:length&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I solved Advent of Code's day 08 with this. This year I learned ✓&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vindarel/bacalisp/tree/master/advent/2024" rel="noopener noreferrer"&gt;AOC 2024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISP-IS-NOT-DEAD" rel="noopener noreferrer"&gt;Learn lisp efficiently! Common Lisp videos&lt;/a&gt; 8+ hours of content in code-driven examples. Thanks &amp;lt;3&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
      <category>adventofcode</category>
    </item>
    <item>
      <title>FTP and SFTP clients for Common Lisp</title>
      <dc:creator>vindarel</dc:creator>
      <pubDate>Sat, 28 Sep 2024 14:48:00 +0000</pubDate>
      <link>https://forem.com/vindarel/ftp-and-sftp-clients-for-common-lisp-1c3b</link>
      <guid>https://forem.com/vindarel/ftp-and-sftp-clients-for-common-lisp-1c3b</guid>
      <description>&lt;p&gt;You thought all companies would provide well-documented web APIs today? Well, some use a blank .docx and (S)FTP.&lt;/p&gt;

&lt;p&gt;For Common Lisp, &lt;a href="https://github.com/pinterface/cl-ftp" rel="noopener noreferrer"&gt;cl-ftp&lt;/a&gt; is one of those libraries that look unmaintained and undocumented, but it works very well, and it is short enough to quickly grab how to use it. It's quite well thought-out, even. It is a pure Lisp library, no system dependencies required.&lt;/p&gt;

&lt;p&gt;For example you can do this to send a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ftp:with-ftp-connection&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;conn&lt;/span&gt; &lt;span class="ss"&gt;:hostname&lt;/span&gt; &lt;span class="nv"&gt;hostname&lt;/span&gt;
                                   &lt;span class="ss"&gt;:username&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;find-ftp-username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                   &lt;span class="ss"&gt;:password&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt;
                                   &lt;span class="ss"&gt;:passive-ftp-p&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ftp:store-file&lt;/span&gt; &lt;span class="nv"&gt;conn&lt;/span&gt; &lt;span class="nv"&gt;local-filename&lt;/span&gt; &lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll have to look at this for more commands: &lt;a href="https://github.com/pinterface/cl-ftp/blob/master/simple-client.lisp" rel="noopener noreferrer"&gt;https://github.com/pinterface/cl-ftp/blob/master/simple-client.lisp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;we have &lt;code&gt;ftp-shell&lt;/code&gt;, &lt;code&gt;ftp-put&lt;/code&gt;, &lt;code&gt;ftp-list&lt;/code&gt;, &lt;code&gt;ftp-get&lt;/code&gt;, &lt;code&gt;ftp-pwd&lt;/code&gt;, and etc for ls, help, cd, dir.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;SFTP&lt;/strong&gt;, we must find another means. I didn't find a pure CL library, and it is annoyingly not straightforward to run a SFTP command with a password on the command line. &lt;a href="https://stackoverflow.com/questions/5386482/how-to-run-the-sftp-command-with-a-password-from-bash-script" rel="noopener noreferrer"&gt;Some solutions exist&lt;/a&gt; and I chose &lt;a href="https://lftp.yar.ru/" rel="noopener noreferrer"&gt;lftp&lt;/a&gt;. It is included in Debian.&lt;/p&gt;

&lt;p&gt;We can run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lftp sftp://user:password@host  -e "put local-file.name; bye"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;or better, with the password in an environment variable:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export LFTP_PASSWORD="just_an_example"
lftp --env-password sftp://user@host  -e "put local-file.name; bye"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In my scripts I need to handle a connection profile, and even several ones for development, so I ended up with code that I replicate from project to project, hence a new utility:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vindarel/lftp-wrapper" rel="noopener noreferrer"&gt;https://github.com/vindarel/lftp-wrapper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="nv"&gt;CL-USER&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;use-package&lt;/span&gt; &lt;span class="ss"&gt;:lftp-wrapper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;;; optional, or:&lt;/span&gt;
&lt;span class="nv"&gt;CL-USER&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:add-package-local-nickname&lt;/span&gt; &lt;span class="ss"&gt;:lftp&lt;/span&gt; &lt;span class="ss"&gt;:lftp-wrapper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;CL-USER&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defvar&lt;/span&gt; &lt;span class="nv"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;make-profile-from-plist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uiop:read-file-form&lt;/span&gt; &lt;span class="s"&gt;"CREDS.lisp-expr"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;PROFILE&lt;/span&gt; &lt;span class="nv"&gt;protocol:&lt;/span&gt; &lt;span class="s"&gt;"sftp"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;login:&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port:&lt;/span&gt; &lt;span class="mi"&gt;10022&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;server:&lt;/span&gt; &lt;span class="s"&gt;"prod.com"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;password?&lt;/span&gt; &lt;span class="nv"&gt;T&amp;gt;&lt;/span&gt;

&lt;span class="nv"&gt;CL-USER&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defvar&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;put&lt;/span&gt; &lt;span class="ss"&gt;:cd&lt;/span&gt; &lt;span class="s"&gt;"I/"&lt;/span&gt; &lt;span class="ss"&gt;:local-filename&lt;/span&gt; &lt;span class="s"&gt;"data.csv"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;PUT&lt;/span&gt; &lt;span class="nv"&gt;cd:&lt;/span&gt; &lt;span class="s"&gt;"I/"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;filename:&lt;/span&gt; &lt;span class="s"&gt;"data.csv"&lt;/span&gt; &lt;span class="nv"&gt;{1007153883}&amp;gt;&lt;/span&gt;

&lt;span class="nv"&gt;CL-USER&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="nv"&gt;profile&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="nv"&gt;success!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We created a profile from credentials on file and from environment variables, we created a command object, and we executed the command for the profile.&lt;/p&gt;

&lt;p&gt;We could wrap many more features from lftp. But I follow needs-driven development. So far, it works and it sends my files.&lt;/p&gt;

</description>
      <category>lisp</category>
      <category>commonlisp</category>
      <category>sftp</category>
    </item>
  </channel>
</rss>
