Why "spinml"
The reason I haven't been posting for almost three weeks was that I needed a deep refactor of the SSG powering this website in order to get the "spinml" (SPINneret Markup Language) feature I wanted the most.
Before showcasing said feature, let me tell you why I chose to this strange contraption instead of Markdown like most real human beans (or at least a simpler contraption):
- I'm now fully Lisp-pilled and can't escape the magnetic grasp of S-expressions.
- Being able to seamlessly blend code/data and communicate between HTML generation and the main
program is incredibly useful; for example, I generate the front page's article list by looping over
dynamically generated feed data (
welcome.spinml
). - But really, what truly made my hacker blood rush is
deftag
, in other words the ability to augment HTML with my own tags.
And if you think that this is just some kind of syntactic macro, something not far from XSLT, you don't know about Lisp macros: we're talking about the entire language being available at compile-time, you can run programs, manipulate files or global state and lots of other dangerous but powerful side-effects inside of those!
So without further ado, here are some of my custom tags:
:h2a
Very simple
(:h2a "Title") ;; Expands into (:h2 "Title" " " (:a :class "h2a-anchor" :href "#h2-X" :id "h2-X" "§"))
With X
being an auto-incrementing counter. Automatic anchors, a decent start.
:clhs
and :posix
When I want to reference something from the Common Lisp HyperSpec or the POSIX spec (which is often), I don't want to tediously fish around and copy-paste the proper URL when a hash table could do it for me. So instead of
(:a :href "https://www.lispworks.com/documentation/HyperSpec/Body/m_declai.htm" (:code "declaim")) (:a :href "https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html" (:code "make")) (:a :href "https://pubs.opengroup.org/onlinepubs/9799919799/functions/execve.html" (:code "execve"))
I can get away with
(:clhs "declaim") (:posix "make") (:posix :type "functions" "execve")
Noice.
:taglist
Now getting to the powerful stuff. This is what powers the tag list (duh) at the top of each
blog post (thus saving me the hassle of creating those :a
with a hardcoded blog
path), but more importantly, it's also the unique tag searched for during blog setup
to create and fill the tag pages!
:img*
The tag that prompted this large overhaul and allowed me to replace this sad mess
(:figure (:a :href "/resources/page/blog/2025/07/music-review-eyehategod-dopesick-1996-/cover.jpg" (:img :class "inset" :alt "Album cover" :src "/resources/page/blog/2025/07/music-review-eyehategod-dopesick-1996-/t/cover.jpg")) (:figcaption (:a :href "/resources/page/blog/2025/07/music-review-eyehategod-dopesick-1996-/cover-eu.jpg" "Alternative European release cover")))
Which implies the following tasks:
- Create the directory chain under
/resources/page
by applying my URL mangling scheme by hand. - Put the images I want to use there.
- If I want a thumbnail, do all the song and dance to get one good enough for my tech autism: downscale in linear light using a RobidouxSharp EWA kernel then encode using a modern JPEG encoder (mozjpeg while waiting for jpegli to get a release and/or JPEG-XL to get supported).
- Paste the new URLs into their
:href
without making any mistake (fat chance). - Grumble that this is machine slave work and that I really ought to automatize this crap.
With the beautiful, pristine even
(:figure (:img* :class "inset" :thumb-if-larger-than '(500 . 750) "cover.jpg") (:figcaption (:img* :as-link "Alternative European release cover" "cover-eu.jpg")))
That does all of this by reading its source images from a neighboring DOC.media/
directory (for a document named DOC.spinml
). Rather pleased with myself.
The hidden difficulty that took most of my time was that this feature hinged on a make
-like incremental rebuild system to avoid producing hundreds of thumbnails
each time I generated the website. But I have that and more, now!
Next in line: a tooltip tag and something to automatically produce syntax highlighted :pre
"code boxes" through pygments or something.