<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nicola Rizzo</title><link>https://www.rizzonicola.com/</link><description>Recent content on Nicola Rizzo</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 30 Jul 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://www.rizzonicola.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Automating Anki Deck Creation with LLMs</title><link>https://www.rizzonicola.com/posts/anki-with-ai/</link><pubDate>Wed, 30 Jul 2025 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/anki-with-ai/</guid><description>&lt;p>Creating high-quality Anki flashcards manually can be time-consuming, especially when dealing with large amounts of content. I&amp;rsquo;ve developed a workflow that leverages Large Language Models (LLMs) to automate much of this process, making it significantly more efficient while maintaining quality control.
The key to this approach is providing clear, detailed prompts that guide the LLM&amp;rsquo;s output. An example of a part of the prompt I use to define the rules that LLMs should follow to create, complete or fix my cards:&lt;/p>
&lt;blockquote>
&lt;p>French-Italian Idioms CSV File Rules&lt;/p>
&lt;p>This document outlines the structure and formatting rules for the &lt;code>french_idioms_italian.csv&lt;/code> file.&lt;/p>
&lt;p>File Format&lt;/p>
&lt;ul>
&lt;li>Fields are separated by semicolons (&lt;code>;&lt;/code>)&lt;/li>
&lt;li>String values containing semicolons, spaces, or special characters are enclosed in double quotes (&lt;code>&amp;quot;&lt;/code>)&lt;/li>
&lt;li>File uses UTF-8 encoding to support special characters&lt;/li>
&lt;/ul>
&lt;p>Columns
The CSV contains the following columns in order:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>French Expression&lt;/strong> - The complete French idiom&lt;/li>
&lt;li>&lt;strong>French Definition&lt;/strong> - The meaning of the idiom in French&lt;/li>
&lt;li>&lt;strong>Italian Translation&lt;/strong> - The equivalent idiom in Italian&lt;/li>
&lt;li>&lt;strong>Example1&lt;/strong> - A complete sentence using the idiom with the full expression visible (not masked)&lt;/li>
&lt;li>&lt;strong>PartialCloze1&lt;/strong> - A sentence where only the beginning of the idiom is masked with &lt;code>[...]&lt;/code>&lt;/li>
&lt;li>&lt;strong>Example2&lt;/strong> - A second complete sentence using the idiom with the full expression visible (not masked)&lt;/li>
&lt;li>&lt;strong>PartialCloze2&lt;/strong> - A sentence where only the ending of the idiom is masked with &lt;code>[...]&lt;/code>&lt;/li>
&lt;li>&lt;strong>FullCloze&lt;/strong> - A sentence where the entire idiom is masked with &lt;code>[...]&lt;/code>&lt;/li>
&lt;li>&lt;strong>Register&lt;/strong> - The formality level of the idiom (Neutre, Informel, Familier)&lt;/li>
&lt;/ol>
&lt;p>Cloze Test Rules&lt;/p>
&lt;ul>
&lt;li>&lt;code>[...]&lt;/code> is used as the placeholder for masked portions of text&lt;/li>
&lt;li>&lt;strong>PartialCloze1&lt;/strong>: Masks the beginning of the expression, keeping the ending visible&lt;/li>
&lt;li>&lt;strong>PartialCloze2&lt;/strong>: Masks the ending of the expression, keeping the beginning visible&lt;/li>
&lt;li>&lt;strong>FullCloze&lt;/strong>: Masks the entire expression&lt;/li>
&lt;li>All cloze examples must maintain grammatical correctness and natural flow&lt;/li>
&lt;li>Example sentences should provide sufficient context to infer the missing expression&lt;/li>
&lt;li>Use completely different sentences for different fields (Example1, Example2, PartialCloze1, PartialCloze2, &amp;gt; FullCloze) to provide varied contexts&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>I use placeholders (&amp;quot;[&amp;hellip;]&amp;quot;) and not cloze card types because I think it&amp;rsquo;s not possible to create cloze cards AND front/back cards in the same note.&lt;/p>
&lt;p>I use CSV files as intermediate files to interact with the content of the decks. I&amp;rsquo;ve tried to use an MCP server (that I wrote using Claude Code, the &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk">MCP TypeScript SDK&lt;/a> and the &lt;a href="https://git.sr.ht/~foosoft/anki-connect">Anki Connect API specification&lt;/a>) to connect LLMs to Anki Connect API and it works well, but it&amp;rsquo;s slow if you interact with thousands of notes. I prefer using LLM to create a Python script that loads the complete CSV file into the deck using the Anki Connect API.&lt;/p>
&lt;p>I use LLMs also to reflect on card types: how to structure them? Which fields should be shown in front? Which ones in back? How to style them? Unfortunately, at the moment, Anki Connect does not allow creating or editing card types, so I had to create/edit the card types by hand.&lt;/p>
&lt;p>It&amp;rsquo;s obviously an iterative process, but using LLM to create Anki decks is, by far, more productive than doing it by hand. I start, for example, by listing French expressions in a new CSV file:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-md" data-lang="md">&lt;span class="line">&lt;span class="cl">French Expression
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">prendre le taureau par les cornes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chercher une aiguille dans une botte de foin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and then I ask an LLM to add a new column, &lt;em>French Definition&lt;/em> for example, following the rules I listed above. When I&amp;rsquo;ve verified a sample of the results, I add the other columns, one by one. And at the end I use the Python script written by the LLM to create a new deck.&lt;/p></description></item><item><title>Notes on A Tour of Webauthn</title><link>https://www.rizzonicola.com/posts/a-tour-of-webauthn-notes/</link><pubDate>Wed, 30 Jul 2025 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/a-tour-of-webauthn-notes/</guid><description>&lt;p id="wip-warning">This is a &lt;a href="https://www.rizzonicola.com/posts/wip">Work In Progress (WIP)&lt;/a>.&lt;/p>
&lt;p>&lt;a href="https://www.imperialviolet.org/tourofwebauthn/tourofwebauthn.html">A Tour of WebAuthn&lt;/a>&lt;/p>
&lt;p>Problems of passwords:&lt;/p>
&lt;ul>
&lt;li>too little entropy to resist brute-forcing&lt;/li>
&lt;li>password database leaks =&amp;gt; hacker can sign in to the site AND credential stuffing&lt;/li>
&lt;li>vulnerable to phishing&lt;/li>
&lt;li>they can leak from many other parts of the software stack (e.g. logging, Javascript-injection attack =&amp;gt; exfiltration)&lt;/li>
&lt;/ul>
&lt;p>U2F and Webauthn are systems of authentication based on public key signature schemes, like ECDSA, RSA, ML-DSA.&lt;/p>
&lt;p>Abstractly, a public key signature scheme provides three operations:&lt;/p>
&lt;p>$$
\text{generate}: \text{random_bits} \mapsto (\text{public_key}, \text{private_key})
$$&lt;/p>
&lt;p>$$
\text{sign}: (\text{private_key}, \text{message}) \mapsto \text{signature}
$$&lt;/p>
&lt;p>$$
\text{verify}: (\text{public_key}, \text{message}, \text{signature}) \mapsto \text{boolean}
$$&lt;/p>
&lt;p>There are also some properties:&lt;/p>
&lt;ul>
&lt;li>it&amp;rsquo;s impossible to compute the private key from the public key&lt;/li>
&lt;li>it&amp;rsquo;s impossible to compute a signature without the private key&lt;/li>
&lt;/ul>
&lt;p>Simple authentication schemes using public key signature:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-mermaid" data-lang="mermaid">sequenceDiagram
participant User
participant Computer
participant Website
User-&amp;gt;&amp;gt;Computer: Enter username
Computer-&amp;gt;&amp;gt;Computer: Run sign(privateKey, &amp;#34;let me in&amp;#34;) → signature
Computer-&amp;gt;&amp;gt;Website: Send {username, signature}
Website-&amp;gt;&amp;gt;Website: Retrieve stored publicKey for username
Website-&amp;gt;&amp;gt;Website: Run verify(publicKey, &amp;#34;let me in&amp;#34;, signature)
alt signature valid
Website-&amp;gt;&amp;gt;User: Sign-in successful
else signature invalid
Website-&amp;gt;&amp;gt;User: Sign-in denied
end
&lt;/code>&lt;/pre></description></item><item><title>2025 - Week #28</title><link>https://www.rizzonicola.com/posts/2025-28/</link><pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/2025-28/</guid><description>&lt;h2 id="a-framework-for-understanding-ai-companies">A Framework for Understanding AI Companies&lt;/h2>
&lt;p>AI companies can be categorized into three distinct groups:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Model companies&lt;/strong> focus on developing and training large language models and foundation models.&lt;/li>
&lt;li>&lt;strong>Infrastructure companies&lt;/strong> build the tools, platforms, and services that enable AI development and deployment.&lt;/li>
&lt;li>&lt;strong>Application companies&lt;/strong> create end-user products and services powered by AI.&lt;/li>
&lt;/ul>
&lt;p>This framework is helpful for understanding how different players in the AI ecosystem create value.&lt;/p>
&lt;p>Sources:&lt;/p>
&lt;ul>
&lt;li>The Pragmatic Engineer podcast episode with Janvi Kalra&lt;/li>
&lt;li>&amp;ldquo;AI Engineering&amp;rdquo; by Chip Huyen&lt;/li>
&lt;/ul>
&lt;h2 id="the-four-pillars-of-product-discovery">The Four Pillars of Product Discovery&lt;/h2>
&lt;p>Product discovery should systematically address four critical risks:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Value risk&lt;/strong>: customers will actually buy or use what you&amp;rsquo;re building?&lt;/li>
&lt;li>&lt;strong>Usability risk&lt;/strong>: users can figure out how to use your product effectively?&lt;/li>
&lt;li>&lt;strong>Feasibility risk&lt;/strong>: your team can actually build the solution?&lt;/li>
&lt;li>&lt;strong>Business viability risk&lt;/strong>: the solution makes sense for your business model and constraints?&lt;/li>
&lt;/ul>
&lt;p>Source: chapter 33 of &amp;ldquo;Inspired&amp;rdquo; by Marty Cagan, which provides a clear framework for de-risking product development before significant investment.&lt;/p>
&lt;h2 id="understanding-temperature-in-language-models">Understanding Temperature in Language Models&lt;/h2>
&lt;p>Language models work by predicting the next token in a sequence of text. They calculate probability distributions across their entire vocabulary to determine what comes next.&lt;/p>
&lt;p>For example, after &amp;ldquo;Hi! How are you?&amp;rdquo; the model might predict &amp;ldquo;Hi&amp;rdquo; with 70% probability, &amp;ldquo;Hello&amp;rdquo; with 15%, &amp;ldquo;Fine&amp;rdquo; with 5%, &amp;ldquo;Why&amp;rdquo; with 0.4%, and so on.&lt;/p>
&lt;p>The challenge is choosing which token to present to users—a process called &lt;em>sampling&lt;/em>. Greedy sampling simply picks the highest probability token (&amp;ldquo;Hi&amp;rdquo; in our example). Probabilistic sampling follows the calculated distribution, presenting &amp;ldquo;Hi&amp;rdquo; 70% of the time, &amp;ldquo;Hello&amp;rdquo; 15% of the time, and so forth.&lt;/p>
&lt;p>The temperature parameter adds flexibility to probabilistic sampling by redistributing these probabilities. Higher temperatures increase the likelihood of rarer tokens, producing more creative and varied responses. Lower temperatures favor the most probable tokens, creating more consistent and predictable outputs.&lt;/p>
&lt;p>Source: Chapter 2 of &amp;ldquo;AI Engineering&amp;rdquo; by Chip Huyen&lt;/p>
&lt;h2 id="what-im-listening-to">What I&amp;rsquo;m Listening To&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://newsletter.pragmaticengineer.com/p/from-software-engineer-to-ai-engineer">The Pragmatic Engineer: From Software Engineer to AI Engineer – with Janvi Kalra&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="what-im-reading">What I&amp;rsquo;m Reading&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://janvikalra.substack.com/p/how-to-evaluate-model-infrastructure">How to evaluate model, infrastructure, and product companies in AI&lt;/a> by Janvi Kalra&lt;/li>
&lt;li>&lt;a href="https://www.indragie.com/blog/i-shipped-a-macos-app-built-entirely-by-claude-code">I Shipped a macOS App Built Entirely by Claude Code&lt;/a> by Indragie Karunaratne&lt;/li>
&lt;li>&lt;em>AI Engineering&lt;/em> by Chip Huyen. I started reading this book this week, and it appears to be an excellent introduction to the world of large language models, providing practical guidance for using these tools more effectively&lt;/li>
&lt;li>&lt;em>Inspired&lt;/em> by Marty Cagan&lt;/li>
&lt;li>&lt;em>Deploy Empathy&lt;/em> by Michele Hansen&lt;/li>
&lt;li>&lt;em>Holly&lt;/em> by Stephen King. I finished reading it this week. Personally, I preferred Fairy Tale and 11/22/63, the two other books I&amp;rsquo;ve read by this author. While it&amp;rsquo;s not a bad book—it&amp;rsquo;s a page-turner like the others—it explores different compelling themes, particularly evil and aging.&lt;/li>
&lt;/ul>
&lt;h2 id="what-im-working-on">What I&amp;rsquo;m Working On&lt;/h2>
&lt;p>I&amp;rsquo;m continuing the development of my macOS app that lets users chat with multiple LLM providers. Following Indragie Karunaratne&amp;rsquo;s inspiring post, I&amp;rsquo;m building it using also Claude Code.&lt;/p>
&lt;p>Using AI agents has improved my productivity in ways that are hard to appreciate without direct experience. The quality of Claude Code often impresses me, though success requires following best practices to avoid getting stuck in unproductive cycles (for example: &lt;a href="https://www.anthropic.com/engineering/claude-code-best-practices">Claude Code - Best practices&lt;/a>).&lt;/p></description></item><item><title>2025 - Weeks #29, #30, #31</title><link>https://www.rizzonicola.com/posts/2025-29-30-31/</link><pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/2025-29-30-31/</guid><description>&lt;h2 id="what-im-listening-to">What I&amp;rsquo;m Listening To&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.startupsfortherestofus.com/episodes/episode-783-bootstrapping-scrapingbee-to-5m-arr-and-an-8-figure-exit">Startups for the Rest of Us - Episode 783 | Bootstrapping ScrapingBee to $5M ARR and an 8-Figure Exit&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.startupsfortherestofus.com/episodes/episode-782-why-i-succeeded-my-10-best-entrepreneurial-decisions">Startups for the Rest of Us - Episode 782 | Why I Succeeded: My 10 Best Entrepreneurial Decisions&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://lexfridman.com/dhh-david-heinemeier-hansson/">Lex Fridman - #474 – DHH: Future of Programming, AI, Ruby on Rails, Productivity &amp;amp; Parenting&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.radiofrance.fr/franceculture/podcasts/la-conversation-scientifique/comment-se-fabriquent-les-idees-recues-a-propos-du-cerveau-8995552">La Conversation scientifique - Comment se fabriquent les idées reçues à propos du cerveau ?&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="what-im-reading">What I&amp;rsquo;m Reading&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://words.filippo.io/passkey-encryption/">Encrypting Files with Passkeys and age, by Filippo Valsorda&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.imperialviolet.org/tourofwebauthn/tourofwebauthn.html">A Tour of WebAuthn, by Adam Langley&lt;/a>. I&amp;rsquo;m taking some notes &lt;a href="https://www.rizzonicola.com/posts/a-tour-of-webauthn-notes/">here&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="what-im-working-on">What I&amp;rsquo;m Working On&lt;/h2>
&lt;p>I&amp;rsquo;m using LLM to help me creating two Anki decks:&lt;/p>
&lt;ul>
&lt;li>English collocations&lt;/li>
&lt;li>French idiomatic expressions&lt;/li>
&lt;/ul>
&lt;p>You can read some notes about how I use AI with Anki &lt;a href="https://www.rizzonicola.com/posts/anki-with-ai/">here&lt;/a>.&lt;/p></description></item><item><title>2025 - Week #27</title><link>https://www.rizzonicola.com/posts/2025-27/</link><pubDate>Thu, 03 Jul 2025 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/2025-27/</guid><description>&lt;h2 id="what-im-listening">What I’m listening&lt;/h2>
&lt;h3 id="startups-for-the-rest-of-us---episode-781">Startups for the Rest of Us - Episode 781&lt;/h3>
&lt;p>&lt;a href="https://www.startupsfortherestofus.com/episodes/episode-781-a-founders-regret-list-12-mistakes-ill-never-make-again">Podcast episode page&lt;/a>&lt;/p>
&lt;p>In this episode, Rob Walling talks about mistakes has done during his founder’s career. I’ve found some interesting points in the episode. I would like to focus on one point in particular here.&lt;/p>
&lt;p>&lt;strong>Mistake #5&lt;/strong>: &amp;ldquo;Trying to do everything solo&amp;rdquo;&lt;/p>
&lt;p>Rob had tried to do everything without hiring full-time employees; firstly completely alone, and later with some contractors, but he says:&lt;/p>
&lt;blockquote>
&lt;p>There was no loyalty and there was no cohesion. The contractors didn’t even really know each other, so it was barely a team. And the approach wound up sucking because my entire job was managing contractors, managing projects, assigning things, checking on things, and it’s like it’s not fun […] I don’t want to just be a paper pusher in essence or a project manager. I want to actually get s**t done with ambitious people who really want to build something incredible&lt;/p>
&lt;/blockquote>
&lt;p>Using his words, he had hired &amp;ldquo;task level thinkers&amp;rdquo;, but he needed also &amp;ldquo;project level and owner level thinkers&amp;rdquo;. He developed this concept in &lt;a href="https://www.startupsfortherestofus.com/episodes/episode-551-task-level-vs-project-level-thinkers-no-such-thing-as-an-autopilot-business-and-more-a-rob-solo-adventure">the episode #551 of his podcast&lt;/a>.&lt;/p>
&lt;p>About task level thinkers, he says:&lt;/p>
&lt;blockquote>
&lt;p>For years, I operated with task-level thinkers, and I was happy [&amp;hellip;] These are folks who were doing design work, folks who were doing administration, folks who were doing email support, developers [&amp;hellip;]&lt;/p>
&lt;/blockquote>
&lt;p>About project level thinkers, he says:&lt;/p>
&lt;blockquote>
&lt;p>But what I realized is I was then doing all the owner-level thinking which was longer-term stuff, and the project-level thinking which was this project needs—this is project management—seven things to happen, so now I get to manage all those people [&amp;hellip;] I hired a couple of people who were more project-thinkers. I can hand an entire project and they would then either manage the resources for me or they could do the whole thing themselves [&amp;hellip;].&lt;/p>
&lt;/blockquote>
&lt;p>About owner level thinkers, he says:&lt;/p>
&lt;blockquote>
&lt;p>[&amp;hellip;] after we were acquired [&amp;hellip;] I started seeing folks working inside a company who were not the C-suite, they were not owners, they were not founders of the company, but they really owned an entire segment and they thought creatively around it. So someone who’s a marketing strategist who ran this whole team of people, wasn&amp;rsquo;t just thinking about projects. Actually, each of his people have their own projects, but he was thinking long-term, what do we need to do in 12–18 months? Coming up with new ideas, listening to the audio books, listening to the podcast, reading the books, and being what I call an owner-level thinker where it’s not about the equity that he owned but it was about ownership of the the results [&amp;hellip;] from the vision to the implementation, and working with the team to do it.&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-im-reading">What I’m reading&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;em>Inspired&lt;/em>, by Marty Cagan. This is a book full of useful information for anyone interested in tech product management&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;em>Deploy Empathy&lt;/em>, by Michele Hansen. The go-to resource for customer interviewing&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;em>Holly&lt;/em>, by Stephen King. Reading a fiction book every now and then is not a bad idea&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="what-im-working-on">What I&amp;rsquo;m working on&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nicolascommonplacebook.substack.com">My Substack newsletter&lt;/a>&lt;/li>
&lt;li>A (closed-source, sorry folks) MacOS app where users can chat with multiple LLM providers using an &lt;a href="https://openrouter.ai/">OpenRouter&lt;/a> key. It&amp;rsquo;s my first MacOS app and I&amp;rsquo;m learning a ton. I&amp;rsquo;m using the same approach that Mitchell Hashimoto is using with his terminal, &lt;a href="https://github.com/ghostty-org/ghostty">Ghostty&lt;/a>. He details the idea &lt;a href="https://mitchellh.com/writing/zig-and-swiftui">in his blog&lt;/a>. He&amp;rsquo;s using Zig, I&amp;rsquo;m using Go.&lt;/li>
&lt;/ul></description></item><item><title>About me</title><link>https://www.rizzonicola.com/about-me/</link><pubDate>Wed, 09 Aug 2023 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/about-me/</guid><description>&lt;p>Hi! I&amp;rsquo;m a software developer. I live in Italy. I&amp;rsquo;m currently working as a remote co-lead dev contractor for a French company, &lt;a href="https://www.helfrich.fr/">Helfrich&lt;/a> (&lt;a href="https://www.helfrich.fr/advango">advanGO project&lt;/a>).&lt;/p>
&lt;ul>
&lt;li>Mastodon: &lt;a href="https://hachyderm.io/@nicolarizzo">https://hachyderm.io/@nicolarizzo&lt;/a> (@nicolarizzo@hachyderm.io)&lt;/li>
&lt;li>LinkedIn: &lt;a href="https://www.linkedin.com/in/rizzonicola/">https://www.linkedin.com/in/rizzonicola/&lt;/a>&lt;/li>
&lt;li>GitHub: &lt;a href="https://github.com/nicolanrizzo">https://github.com/nicolanrizzo&lt;/a>&lt;/li>
&lt;li>Send me an email: heavy.plan1680 at fastmail dot com&lt;/li>
&lt;/ul></description></item><item><title>WIP - Work In Progress in my blog</title><link>https://www.rizzonicola.com/posts/wip/</link><pubDate>Tue, 08 Aug 2023 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/wip/</guid><description>&lt;p>In my blog I prefer to share as soon as possible what I&amp;rsquo;m working on and what I&amp;rsquo;m writing about. When there is a small amount of value in what I&amp;rsquo;m writing, I don&amp;rsquo;t wait to end it to publish it. Obviously these WIP (Work In Progress) pieces could be incomplete (duh!), not entirely correct (waiting factchecking), and/or readable (waiting editing).&lt;/p>
&lt;p>I use this Hugo shortcode&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{{% wip %}}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>to include WIP sections in my posts. This shortcode corresponds to this rendered section:&lt;/p>
&lt;p id="wip-warning">This is a &lt;a href="https://www.rizzonicola.com/posts/wip">Work In Progress (WIP)&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;ve also found a simple solution for a problem in the RSS feed: when I&amp;rsquo;m working on a WIP piece I dont&amp;rsquo;t want to send an RSS item for every single editing, but I would like to create a new RSS item after an important update. To do that, I use the &lt;code>guid&lt;/code> tag (&lt;a href="https://www.rssboard.org/rss-specification#ltguidgtSubelementOfLtitemgt">https://www.rssboard.org/rss-specification#ltguidgtSubelementOfLtitemgt&lt;/a>) in the RSS Hugo template:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;rss&lt;/span> &lt;span class="na">version=&lt;/span>&lt;span class="s">&amp;#34;2.0&amp;#34;&lt;/span> &lt;span class="na">xmlns:atom=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2005/Atom&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;channel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;item&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {{ if (not (.Params.rssGuid)) }}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;guid&amp;gt;&lt;/span>{{ .Permalink }}&lt;span class="nt">&amp;lt;/guid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {{ else }}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;guid&amp;gt;&lt;/span>{{ .Params.rssGuid }}&lt;span class="nt">&amp;lt;/guid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {{ end }}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;description&amp;gt;&lt;/span>{{ .Content | html }}&lt;span class="nt">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/item&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/channel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/rss&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and in the frontmatter of the WIP post I write:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">rssGuid&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">f0632b25-fb5c-404c-977c-c0e7f17f1afb&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I use UUIDv4 for the rssGuid. When I want to send an update to my RSS feed followers, I generate a new UUID and remplace the value in the rssGuid parameter.&lt;/p>
&lt;figure>
&lt;img src="https://www.rizzonicola.com/posts/wip/wip-rss.webp" alt="A double RSS item (Published writers&amp;#39; notebooks) in my feed after changing the guid value" style="max-width: 100%" />
&lt;figcaption>A double RSS item (Published writers&amp;#39; notebooks) in my feed after changing the guid value&lt;/figcaption>
&lt;/figure></description></item><item><title>Compile and load SQLite extensions</title><link>https://www.rizzonicola.com/posts/tils/compile-and-load-sqlite-ext/</link><pubDate>Sat, 05 Aug 2023 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/tils/compile-and-load-sqlite-ext/</guid><description>&lt;p>How to compile the &lt;a href="https://www.sqlite.org/src/file?name=ext/misc/uuid.c">UUID extension&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wget https://www.sqlite.org/src/tarball/sqlite.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar xzf sqlite.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir bld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> bld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">../sqlite/configure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gcc -g -I. -fPIC -dynamiclib ../sqlite/ext/misc/uuid.c -o uuid.dylib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Run the sqlite3 command-line program:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">./sqlite3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Try to use the UUID extension without loading it:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sqlite&amp;gt; select uuid();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Parse error: no such function: uuid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> select uuid();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ^--- error here
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Load the UUID extension and use it:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">sqlite&amp;gt; .load ./uuid
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sqlite&amp;gt; select uuid();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">945ef12d-f428-4b8a-831c-3a2fbe7c60b4
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Tested on an Intel Macbook Pro.&lt;/p>
&lt;p>Source: &lt;a href="https://stackoverflow.com/a/61850934">https://stackoverflow.com/a/61850934&lt;/a>&lt;/p></description></item><item><title>Essays One, by Lydia Davis</title><link>https://www.rizzonicola.com/books/essays-one-lydia-davis/</link><pubDate>Sat, 05 Aug 2023 00:00:00 +0000</pubDate><guid>e79772bc-afc7-4941-aace-f06b61055863</guid><description>&lt;p id="wip-warning">This is a &lt;a href="https://www.rizzonicola.com/posts/wip">Work In Progress (WIP)&lt;/a>.&lt;/p>
&lt;h2 id="recommended-books-by-the-author">Recommended books by the author&lt;/h2>
&lt;h3 id="chapter-thirty-recommendations-for-good-writing-habits">Chapter &amp;ldquo;Thirty Recommendations for Good Writing Habits&amp;rdquo;&lt;/h3>
&lt;ul>
&lt;li>&amp;ldquo;The 3 A.M. Epiphany&amp;rdquo;, by Brian Kiteley (a book of writing exercises for fiction writers)&lt;/li>
&lt;li>&amp;ldquo;Works and Days&amp;rdquo;, by &lt;a href="https://en.wikipedia.org/wiki/Bernadette_Mayer">Bernadette Mayer&lt;/a> (a diary, with brief entries in prose and poetry from April into June)&lt;/li>
&lt;li>&amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/My_Struggle_(Knausg%C3%A5rd_novels)">My Struggle&lt;/a>&amp;rdquo; by &lt;a href="https://en.wikipedia.org/wiki/Karl_Ove_Knausg%C3%A5rd">Karl Ove Knausgård&lt;/a> (Norwegian author) (a series of six autobiographical novels)&lt;/li>
&lt;li>&amp;ldquo;Artful Sentences: Syntax as Style&amp;rdquo; by &lt;a href="https://en.wikipedia.org/wiki/Virginia_Tufte">Virginia Tufte&lt;/a>&lt;/li>
&lt;li>&amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Autobiography_of_Red">Autobiography of Red&lt;/a>&amp;rdquo;, by &lt;a href="https://en.wikipedia.org/wiki/Anne_Carson">Anne Carson&lt;/a> (a narrative poem)&lt;/li>
&lt;li>&amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/A_Death_in_the_Family">Death in the Family&lt;/a>&amp;rdquo;, by &lt;a href="https://en.wikipedia.org/wiki/James_Agee">James Agee&lt;/a>&lt;/li>
&lt;li>&amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/The_Autobiography_of_an_Unknown_Indian">The Autobiography of an Unknown Indian&lt;/a>&amp;rdquo;, by &lt;a href="https://en.wikipedia.org/wiki/Nirad_C._Chaudhuri">Nirad C. Chaudhuri&lt;/a>&lt;/li>
&lt;li>&amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Let_Us_Now_Praise_Famous_Men">Let Us Now Praise Famous Men&lt;/a>&amp;rdquo;, by &lt;a href="https://en.wikipedia.org/wiki/James_Agee">James Agee&lt;/a>&lt;/li>
&lt;li>&amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/A_House_for_Mr_Biswas">A House for Mr. Biswas&lt;/a>&amp;rdquo;, by &lt;a href="https://en.wikipedia.org/wiki/V._S._Naipaul">V. S. Naipaul&lt;/a> (notable for its descriptions)&lt;/li>
&lt;/ul>
&lt;h2 id="other-recommended-authors">Other recommended authors&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Barbara_Pym">Barbara Pym&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Cormac_McCarthy">Cormac McCarthy&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Thomas_Hardy">Thomas Hardy&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Anthony_Trollope">Anthony Trollope&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Grace_Paley">Grace Paley&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Denis_Johnson">Denis Johnson&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>How to create tables with primary key UUID columns in SQLite</title><link>https://www.rizzonicola.com/posts/tils/sqlite-auto-uuid/</link><pubDate>Sat, 05 Aug 2023 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/tils/sqlite-auto-uuid/</guid><description>&lt;p>After &lt;a href="https://www.rizzonicola.com/posts/tils/compile-and-load-sqlite-ext/">loading the UUID extension&lt;/a>, in SQLite you can create tables with a UUID primary key with an automatic value defined by its default value:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">my_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">uuid&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">blob&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">primary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">key&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">default&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uuid_blob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uuid&lt;/span>&lt;span class="p">())),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Something like autoincrementing for UUID columns.&lt;/p></description></item><item><title>Published writers' notebooks</title><link>https://www.rizzonicola.com/books/authors-notebooks/</link><pubDate>Sat, 05 Aug 2023 00:00:00 +0000</pubDate><guid>f0632b25-fb5c-404c-977c-c0e7f17f1afb</guid><description>&lt;p id="wip-warning">This is a &lt;a href="https://www.rizzonicola.com/posts/wip">Work In Progress (WIP)&lt;/a>.&lt;/p>
&lt;ul>
&lt;li>The Weight of the World, by &lt;a href="https://en.wikipedia.org/wiki/Peter_Handke">Peter Handke&lt;/a> (even if he&amp;rsquo;s a &lt;a href="https://en.wikipedia.org/wiki/Peter_Handke#Controversies">controversial writer&lt;/a>)&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Virginia_Woolf">Virginia Woolf&lt;/a> (A Writer&amp;rsquo;s Diary, Diaries)&lt;/li>
&lt;li>The Diaries of &lt;a href="https://en.wikipedia.org/wiki/Franz_Kafka">Franz Kafka&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://fr.wikisource.org/wiki/Journal_(Eug%C3%A8ne_Delacroix)">Journal de Eugène Delacroix&lt;/a> (in French, &amp;ldquo;The Journal of Eugène Delacroix&amp;rdquo; in English. Yeah, he&amp;rsquo;s an artist, not a writer, but his notebook is interesting)&lt;/li>
&lt;li>&lt;a href="https://it.wikisource.org/wiki/Pensieri_di_varia_filosofia_e_di_bella_letteratura">Zibaldone di pensieri&lt;/a>, by Giacomo Leopardi (in Italian)&lt;/li>
&lt;li>The Unabridged Journals of &lt;a href="https://en.wikipedia.org/wiki/Sylvia_Plath">Sylvia Plath&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Automating PDF Table of Contents Creation Using AI and PDFtk</title><link>https://www.rizzonicola.com/posts/pdf-table-of-contents-with-ai-and-pdftk/</link><pubDate>Sat, 25 Feb 2023 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/pdf-table-of-contents-with-ai-and-pdftk/</guid><description>&lt;p>Adding a Table of Contents (ToC) to a PDF file can be a tedious process, but with the right tools, it can be almost automated. In this guide, we&amp;rsquo;ll use AI to generate the ToC entries from images and PDFtk to integrate them into the PDF.&lt;/p>
&lt;h2 id="step-1-create-a-csv-file-containing-the-table-of-contents-entries">Step 1: Create a CSV File Containing the Table of Contents Entries&lt;/h2>
&lt;p>To generate the ToC entries, we can use AI to extract them from images of the book’s table of contents. Use a prompt like the one below:&lt;/p>
&lt;hr>
&lt;p>&lt;em>You are a system that, given an image of part of a book&amp;rsquo;s table of contents, extracts the data and tabulates it. The columns are:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;em>level (1 for chapters, 2 for sections)&lt;/em>&lt;/li>
&lt;li>&lt;em>page (for chapters, use the page of the first section)&lt;/em>&lt;/li>
&lt;li>&lt;em>title&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>&lt;em>The output must be the contents of a CSV file.&lt;/em>&lt;/p>
&lt;p>&lt;em>Do not respond with anything other than the contents of the CSV file. All provided images must be tabulated without asking for confirmation.&lt;/em>&lt;/p>
&lt;hr>
&lt;p>Customize the prompt as needed and send it to an AI model along with images of the book’s table of contents. The AI will return structured text, which you can save as a CSV file.&lt;/p>
&lt;h2 id="step-2-check-and-clean-the-csv-file">Step 2: Check and Clean the CSV File&lt;/h2>
&lt;p>Before converting the CSV file to a format suitable for PDFtk, review the extracted data. Ensure that:&lt;/p>
&lt;ul>
&lt;li>The page numbers are correct.&lt;/li>
&lt;li>The section titles are properly formatted.&lt;/li>
&lt;li>There are no missing or extra entries.&lt;/li>
&lt;/ul>
&lt;h2 id="step-3-convert-csv-to-pdftk-table-of-contents-format">Step 3: Convert CSV to PDFtk Table of Contents Format&lt;/h2>
&lt;p>Once the CSV file is cleaned, convert it into the format required by PDFtk using the following Python script:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">csv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define the path to your CSV file and the output text file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">csv_file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;bookmarks.csv&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">output_file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;bookmarks.txt&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Open the CSV file and read the content&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">csv_file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">newline&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">reader&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">csv&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Open the output text file in write mode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output_file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;w&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Loop through each row in the CSV file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">reader&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Extract title, page number, and bookmark level&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">title&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">row&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">page_number&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">row&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bookmark_level&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">row&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Write the formatted bookmark information to the text file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;BookmarkBegin&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;BookmarkTitle: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;BookmarkLevel: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">bookmark_level&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;BookmarkPageNumber: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">page_number&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Bookmarks have been written to &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">output_file&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This script transforms the structured CSV data into a format that PDFtk can interpret for adding bookmarks to the PDF.&lt;/p>
&lt;h2 id="step-4-extract-data-from-the-pdf-file-using-pdftk">Step 4: Extract Data from the PDF File Using PDFtk&lt;/h2>
&lt;p>To integrate the Table of Contents, first extract the existing PDF metadata with the following command:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pdftk input.pdf dump_data_utf8 &amp;gt; in-utf8.info
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This will produce a file containing the metadata of the PDF, which we will modify in the next step.&lt;/p>
&lt;h2 id="step-5-edit-the-pdftk-data-file">Step 5: Edit the PDFtk Data File&lt;/h2>
&lt;p>Open the extracted data file and insert the Table of Contents entries obtained in Step 3. Ensure that:&lt;/p>
&lt;ul>
&lt;li>The entries are properly structured.&lt;/li>
&lt;li>They align with the correct page numbers.&lt;/li>
&lt;li>The hierarchy is maintained (if applicable).&lt;/li>
&lt;/ul>
&lt;h2 id="step-6-generate-the-final-pdf-with-the-table-of-contents">Step 6: Generate the Final PDF with the Table of Contents&lt;/h2>
&lt;p>Finally, apply the modified data file to the original PDF using the following command:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pdftk input.pdf update_info_utf8 in-utf8.info output out.pdf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This will create a new version of the PDF with the Table of Contents integrated.&lt;/p></description></item><item><title>How to improve performances for SQL queries with IN and thousands of values</title><link>https://www.rizzonicola.com/posts/tils/sql-in-temporary-table/</link><pubDate>Wed, 09 Nov 2022 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/tils/sql-in-temporary-table/</guid><description>&lt;p>Today I&amp;rsquo;ve had to optimize an SQL query with this structure:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tbl&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tbl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">IN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">435&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">3456&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">77432&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- thousands of values here
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>A very simple query, but having thousands of values after &lt;code>IN&lt;/code> causes very bad performances.&lt;/p>
&lt;p>Some Internet searches have allowed me to find an equivalent query to do that, but faster:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TEMPORARY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">PRIMARY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">KEY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">INSERT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">435&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3456&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">77432&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.....&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- thousands of values here
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tbl&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">INNER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tbl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">DROP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tmp&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">-- optional, it depends on following requests
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I&amp;rsquo;ve had to create a &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/create-temporary-table.html">temporary table&lt;/a> and insert into it all IDs and INNER JOIN it. Optionally, you can drop the table after the request, but the table lives only during the session anyway.&lt;/p></description></item><item><title>About SVGs flexibility: dark mode</title><link>https://www.rizzonicola.com/posts/tils/about-svg-flexibility-dark-mode/</link><pubDate>Mon, 07 Nov 2022 00:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/tils/about-svg-flexibility-dark-mode/</guid><description>&lt;p>As an owner of a cheap Wacom tablet, it&amp;rsquo;s quite simple for me to create &amp;ldquo;handmade&amp;rdquo; SVG files using, for example, a vector graphics editor, like Inkscape, Adobe Illustrator or Affinity Designer. After creating the file, with a text editor or IDE you can open it and read its code:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34; standalone=&amp;#34;no&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;!DOCTYPE svg PUBLIC &amp;#34;-//W3C//DTD SVG 1.1//EN&amp;#34; &amp;#34;http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd&amp;#34;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;svg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">width=&lt;/span>&lt;span class="s">&amp;#34;100%&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">height=&lt;/span>&lt;span class="s">&amp;#34;100%&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">viewBox=&lt;/span>&lt;span class="s">&amp;#34;0 0 516 517&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">version=&lt;/span>&lt;span class="s">&amp;#34;1.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2000/svg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:xlink=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/1999/xlink&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xml:space=&lt;/span>&lt;span class="s">&amp;#34;preserve&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:serif=&lt;/span>&lt;span class="s">&amp;#34;http://www.serif.com/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">style=&lt;/span>&lt;span class="s">&amp;#34;fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;path&lt;/span> &lt;span class="na">d=&lt;/span>&lt;span class="s">&amp;#34;M176.207,107.015c2.995,...&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;path&lt;/span> &lt;span class="na">d=&lt;/span>&lt;span class="s">&amp;#34;M180.577,208.347c6.706,-20.119 13.374,...&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/svg&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>It turns out that you could change this code and adapt to your needs. For example, you can style it to adapt its color according to the dark mode option:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">svg&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">path&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fill&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">black&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">body&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">dark&lt;/span> &lt;span class="nt">path&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fill&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">white&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">style&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">d&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">fill&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;currentcolor&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">svg&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>In this code snippet, the CSS class &lt;code>dark&lt;/code> is the class I use to activate the dark mode in the body.&lt;/p>
&lt;figure>
&lt;img src="https://www.rizzonicola.com/posts/tils/about-svg-flexibility-dark-mode/svgs-with-dark-mode.webp" alt="The same SVG file with the dark mode option enabled and disabled" style="max-width: 100%" />
&lt;figcaption>The same SVG file with the dark mode option enabled and disabled&lt;/figcaption>
&lt;/figure></description></item><item><title>Document your project adding Sphinx to docker-compose.yml</title><link>https://www.rizzonicola.com/posts/sphinx-docker/</link><pubDate>Mon, 02 Sep 2019 12:00:00 +0000</pubDate><guid>https://www.rizzonicola.com/posts/sphinx-docker/</guid><description>&lt;p>I strongly believe that a web application project has to be thoroughly documented. And that&amp;rsquo;s more important for larger and longer project, where developers come and go in an unstoppable turnover that risks to slow down the project. A documentation is the only chance for new developers to become rapidly productive.&lt;/p>
&lt;p>But how we, as developers, can document a project? What tools exist out there that can facilitate this task? Well, there are dozens of tools. One of them is &lt;a href="https://www.sphinx-doc.org/en/master/">Sphinx, the Python Documentation Generator&lt;/a>. This library can be used to document everything you want. You can think it as a static site generator, like Jekyll, Hugo or Gatsby, but more oriented to technical documentation. You can host it somewhere, like on the &lt;a href="https://readthedocs.org/">Read The Docs&lt;/a>, or keep it locally. In this article I will explain how to use it locally with a project already using Docker Compose.&lt;/p>
&lt;h2 id="the-dockerfile">The Dockerfile&lt;/h2>
&lt;p>Let&amp;rsquo;s begin writing a simple Dockerfile for Sphinx:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-Dockerfile" data-lang="Dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="k">FROM&lt;/span>&lt;span class="s"> alpine:latest&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /etc/&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> mkdir -p /etc/Sphinx/build&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apk add --no-cache python3 make git&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> pip3 install git+https://github.com/sphinx-doc/sphinx &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> pip3 install sphinx-autobuild&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">CMD&lt;/span> sphinx-autobuild -b html --host 0.0.0.0 --port &lt;span class="m">80&lt;/span> /etc/Sphinx/source /etc/Sphinx/build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>Here, after using the latest Alpine image and creating some folders, I install some dependencies (python3, make, git); after that, I install sphinx and its autobuild. The &lt;a href="https://github.com/GaretJax/sphinx-autobuild">Sphinx-autobuild project&lt;/a> automatically rebuild the documentation after a change in the documentation folder is detected, and it works like a charm also with Docker. With its livereload web server, this library serves also the documentation web page, allowing you to access to the documentation with a browser visiting a URL like http://localhost:8100.&lt;/p>
&lt;h2 id="the-docker-composeyml-file">The docker-compose.yml file&lt;/h2>
&lt;p>Let&amp;rsquo;s add now a service to the &lt;code>docker-compose.yml&lt;/code> already used in the application project:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yml" data-lang="yml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;3&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">service_doc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_doc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker/doc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./doc:/etc/Sphinx/source&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">8100&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>The folder structure of the project is something like:&lt;/p>
&lt;pre tabindex="0">&lt;code>.
├── doc
├── docker
│ └── doc
│ └── Dockerfile
└── docker-compose.yml
&lt;/code>&lt;/pre>&lt;p>I&amp;rsquo;ve defined a volume in the folder &lt;code>./doc&lt;/code>, where there will be the documentation folders and files. The Sphinx Dockerfile presented above is in the folder &lt;code>./docker/doc&lt;/code> and the &lt;code>docker-compose.yml&lt;/code> is in the root folder. The URL to use to visit the documentation is http://localhost:8100.&lt;/p>
&lt;h2 id="the-sphinx-quickstart">The Sphinx quickstart&lt;/h2>
&lt;p>I can now run the command:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker-compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>With this command, I&amp;rsquo;ve created a container with Sphinx. To continue, I have to create a configuration file, &lt;code>conf.py&lt;/code>, in the folder &lt;code>./doc&lt;/code>. To do that, I&amp;rsquo;ve run the following command in the container (after running &lt;code>docker-compose exec service_doc /bin/sh&lt;/code>):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sphinx-quickstart&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>
&lt;p>After answering some questions about my project, the command generates some files. I&amp;rsquo;ve moved the autogenerated &lt;code>conf.py&lt;/code> and &lt;code>index.rst&lt;/code> in the &lt;code>/etc/Sphinx/source&lt;/code> folder in the container:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mv conf.py /etc/Sphinx/source
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mv index.rst /etc/Sphinx/source&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>
&lt;h2 id="writing-the-documentation-using-sphinx">Writing the documentation using Sphinx&lt;/h2>
&lt;p>We can now access the (empty) documentation at http://localhost:8100. If we try changing the &lt;code>index.rst&lt;/code> file, after some seconds the browser auto refresh to show the updated documentation. Following the &lt;a href="https://www.sphinx-doc.org/en/master/usage/quickstart.html">Sphinx documentation site&lt;/a> you can now document your project using Sphinx.&lt;/p></description></item></channel></rss>