<?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>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>LinkedIn: &lt;a href="https://www.linkedin.com/in/rizzonicola/">https://www.linkedin.com/in/rizzonicola/&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>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>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>