<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Reflections on Software Engineering</title><link href="https://iwillig.github.io/blog/" rel="alternate"/><link href="https://iwillig.github.io/feed.xml" rel="self"/><id>urn:uuid:56b5eaaa-36d9-33cb-8e9e-e3c8f63f8390</id><updated>2026-02-21T00:00:00Z</updated><author><name/></author><entry><title>One year of LLM usage with Clojure</title><link href="https://iwillig.github.io/blog/one-year-of-llm-usage-with-clojure/" rel="alternate"/><updated>2026-02-21T00:00:00Z</updated><author><name>Ivan Willig</name></author><id>urn:uuid:c6566e11-c696-39cc-a799-3195acd138af</id><content type="html">&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;&lt;p&gt;This essay is a reflection on using LLM agents with the Clojure programming language. At &lt;a href="https://www.shortcut.com/"&gt;Shortcut&lt;/a&gt;, we have spent the last year building &lt;a href="https://www.korey.ai/"&gt;Korey&lt;/a&gt;, an LLM agent focused on product management. During that time, we have attempted to use different LLM agents to work with our rather large Clojure code base, ~250,000–300,000 lines of Clojure code. In doing so, we discovered a lot. I hope this essay can help people in the Clojure community and in other unorthodox languages more generally take more advantage of LLM tools for their work. I would break our approach down into eras:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Early adoption and struggles&lt;/li&gt;
&lt;li&gt;The doldrums of Claude Code&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/bhauman/clojure-mcp"&gt;clojure-mcp&lt;/a&gt; revolution&lt;/li&gt;
&lt;li&gt;Skills, Prompts, and &lt;a href="https://opencode.ai/"&gt;OpenCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;System prompt refinements&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/badlogic/pi-mono"&gt;PI Agent&lt;/a&gt; and liberation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="early-adoption-and-struggles"&gt;Early Adoption and Struggles&lt;/h2&gt;&lt;p&gt;It is commonly understood that LLM models are more effective with languages that dominate the training dataset. &lt;a href="https://arxiv.org/abs/2308.13354"&gt;Research has shown&lt;/a&gt; that model performance varies significantly based on the volume of training data for each programming language, with Python, JavaScript, and TypeScript being heavily represented in public code repositories. This fact is and was concerning to me. For one, at &lt;a href="https://www.shortcut.com/"&gt;Shortcut&lt;/a&gt; we have a large Clojure code base that we have grown, nurtured, and maintained over the last 11 years. We don't have the time, money, or interest in rewriting this to Python or TypeScript simply because state-of-the-art models prefer these languages. Additionally, to me, it does not seem like a good business move to throw out working code.&lt;/p&gt;
&lt;p&gt;With that in mind, we decided to try to teach Claude Code how to write Clojure code well. Quickly, we realized that certain aspects of the way Claude Code is structured by default make it very difficult to work with a large code base like ours. An example of this is that Claude will run the entire test suite after each thing it implements, and running the entire test suite locally on our machine takes several minutes at this point, unfortunately. As Clojure engineers, we prefer tight feedback loops on the REPL. This several-minute-long test suite run was unacceptable to us.&lt;/p&gt;
&lt;p&gt;We began to tweak our &lt;a href="https://agents.md/"&gt;&lt;strong&gt;AGENT.md&lt;/strong&gt;&lt;/a&gt; file, teaching Claude Code about how Clojure and Clojure's data structures work. With that, we were able to narrow the static verification steps that Claude Code made after each point in its implementation process. We noticed large improvements in our performance and our iterative process at this point.&lt;/p&gt;
&lt;h2 id="the-doldrums-of-claude-code"&gt;The doldrums of Claude Code&lt;/h2&gt;&lt;p&gt;At this point we felt using Claude Code was mostly functional, and we were capable of achieving a certain level of development flow with Claude Code. Claude used our large code base itself as a model of how it should write Clojure code, although we often had to prompt it to do so. I began experimenting with the best ways of prompting the LLM agent. One of the things I discovered is that specifying in detail what the LLM agent should do is critical; you can't leave any ambiguity.&lt;/p&gt;
&lt;p&gt;However, we still struggled with certain aspects of how Claude approached software engineering. For example, we noticed that Claude often leapt ahead before looking. Claude would write a bunch of code — potentially several thousand lines over a few minutes — and then attempt to verify it. The problem is that because of hallucinations or misunderstandings, the new code would often contain errors. At that time, roughly six months ago, Claude was poor at debugging these errors.&lt;/p&gt;
&lt;p&gt;Additionally, we observed a pattern: when Claude ran into an error, it often solved the problem by adding more complexity, which, as software engineers, we know is rarely the right approach. Consequently, we would see Claude spend a lot of time and tokens debugging a problem it created, and it couldn't resolve it because the code didn't actually belong in our code base. This leap-before-looking severely limited Claude's effectiveness in our code base.&lt;/p&gt;
&lt;p&gt;We also noticed some more fundamental flaws with the way Claude—this was Sonnet 3.5 at the time—was approaching Clojure code. One thing we commonly notice is that Sonnet defaults to an imperative programming approach, which we know does not work well in Clojure. For example, we discovered, embedded in a large code change, a &lt;code&gt;doseq&lt;/code&gt; with an &lt;code&gt;atom&lt;/code&gt; where a &lt;code&gt;map&lt;/code&gt;, or a &lt;code&gt;reduce&lt;/code&gt; would be preferable. These issues were problematic for us because Claude can generate a lot of code, which is very difficult to undo. Ultimately, we want the LLM to generate the correct code in the first place. So we faced the dilemma: how do we achieve better functional Clojure code from the LLM? The next step was to explore certain avenues to achieve that.&lt;/p&gt;
&lt;h2 id="the-clojure-mcp-revolution"&gt;The clojure-mcp revolution&lt;/h2&gt;&lt;p&gt;The first step I took was to explore the &lt;a href="https://github.com/bhauman/clojure-mcp"&gt;Clojure MCP&lt;/a&gt; tool. This tool exposed a set of editing and evaluation features that greatly reduce AI hallucinations, and with that we were able to ground the LLM better in our code base. The &lt;a href="https://github.com/bhauman/clojure-mcp"&gt;Clojure MCP&lt;/a&gt;'s edit functionality was essential for preventing invalid parentheses, syntax errors, and other issues from entering our code base.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/bhauman/clojure-mcp"&gt;Clojure MCP&lt;/a&gt; fundamentally altered my belief in the ability of LLMs to write effective Clojure code. Previously, I was struggling and frustrated; working with Clojure and LLMs was a constant source of hallucinated functions, invalid syntax, and a generally unpleasant experience, with a lot of rework and misdirection. &lt;a href="https://github.com/bhauman/clojure-mcp"&gt;Clojure MCP&lt;/a&gt; really changed that. Thanks a ton to &lt;a href="https://github.com/bhauman"&gt;Bruce Hauman&lt;/a&gt; for building Clojure MCP and Clojure MCP Light and releasing them as open-source tools.&lt;/p&gt;
&lt;h2 id="skills-prompts-and-opencode"&gt;Skills, Prompts and OpenCode&lt;/h2&gt;&lt;p&gt;The next step I took toward achieving better LLM output was to evaluate how &lt;a href="https://agentskills.io/home"&gt;Anthropic's skill&lt;/a&gt; system works.  I also developed a tool I ended up calling &lt;a href="https://github.com/iwillig/clojure-skills"&gt;Clojure Skills&lt;/a&gt;. Clojure Skills was envisioned as a SQLite database with a command-line interface that would allow the LLM agent and the human to search through a set of anthropic-style skills. These skills would inform the LLM agent about patterns, idioms, specific libraries, and whatnot, hoping that it would produce much better output and that I would spend a lot less time debugging. This was also the point when I started experimenting a little with tools like &lt;a href="https://opencode.ai/"&gt;OpenCode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What's interesting about &lt;a href="https://opencode.ai/"&gt;OpenCode&lt;/a&gt; is that it lets you easily define your own system prompt. So at this point I defined a &lt;a href="https://github.com/iwillig/clojure-skills/blob/main/prompts/clojure_skill_builder.md"&gt;system prompt&lt;/a&gt; for building a skill that would dynamically load the library onto the Clojure REPL and build a skill for that library.&lt;/p&gt;
&lt;p&gt;Here is an example of using OpenCode with a custom system prompt:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;$schema&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://opencode.ai/config.json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;clojure&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Expert Clojure developer with REPL-driven workflow&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;model&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anthropic/claude-sonnet-4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;prompt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{file:./SYSTEM.md}&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;default_agent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;clojure&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It was during the process of building my own skill system and using those skills day-to-day that I noticed a fundamental issue: over long LLM sessions with large context windows, the knowledge and the skill didn't stay very sticky. The LLM initially uses the correct skills and patterns, but eventually it starts to forget, ignore, and just do its own thing.&lt;/p&gt;
&lt;p&gt;After some research I discovered that this problem is documented in the literature. In their 2023 paper &lt;a href="https://arxiv.org/abs/2307.03172"&gt;"Lost in the Middle: How Language Models Use Long Contexts"&lt;/a&gt;, Liu et al. demonstrated that LLMs effectively have a U-shaped memory curve: the most recent conversation turns and the system prompt are weighted more heavily than the middle of the conversation. Consequently, when skills are loaded mid-conversation, the LLM often fails to follow them.&lt;/p&gt;
&lt;p&gt;I began experimenting within the &lt;a href="https://github.com/iwillig/clojure-skills"&gt;clojure-skills&lt;/a&gt; system with a concept I call "prompt packing." I saw on Reddit that other people were putting all the knowledge of their code base directly into the system prompt. That made immediate sense given what we know about context windows. So I tried both inlining and referencing skills in the system prompt. &lt;a href="https://opencode.ai/"&gt;OpenCode&lt;/a&gt; let me carefully curate the skills in my system prompt.&lt;/p&gt;
&lt;p&gt;With this approach I achieved a new level of effectiveness: I was able to one-shot more and more tasks with the LLM than I ever could with Claude Code alone.&lt;/p&gt;
&lt;h2 id="system-prompts-refinements-and-prompt-evals"&gt;System Prompts Refinements and Prompt Evals&lt;/h2&gt;&lt;p&gt;At this point in our development of &lt;a href="https://www.korey.ai/"&gt;Korey&lt;/a&gt;, it was time to tweak the system prompt for Korey itself. I was lucky enough to be assigned this task at work, and I spent a couple of weeks understanding how people evaluate system prompts and how they make them more effective for their task. I then drew the connection between my engineering system prompt and a prompt-evaluation system. This was key to our development for iterating and evaluating how we use LLMs and how our system prompt can become more effective. A helpful resource for this type of research is &lt;a href="https://hamel.dev/"&gt;hamel.dev&lt;/a&gt;. Hamel Husain is a great communicator and writer. He was critical in my understanding of how to think about refining and defining system prompts, especially for working with code. Thanks, Hamel.&lt;/p&gt;
&lt;p&gt;At this point, I started working on what eventually became my &lt;a href="https://github.com/iwillig/clojure-system-prompt"&gt;Clojure system prompt&lt;/a&gt; project. This was a prompt that we iterated a lot internally at &lt;a href="https://www.shortcut.com/"&gt;Shortcut&lt;/a&gt;, and I have released it as an open-source project. I hope other Clojure developers will find this a helpful starting point to devise their own system prompts. One thing you can notice is I use the REPL extensively to ground truth, aka prevent hallucinations, before the LLM agent writes any code. This was key for transforming what was an incredibly frustrating experience into a much smoother and more graceful LLM interaction.&lt;/p&gt;
&lt;p&gt;Another step I took was to deepen the ability of the LLM to use the Clojure platform itself. Clojure is designed to be interacted with from the REPL. This turns out to be a huge advantage when working with an LLM. For example, instead of defining a new skill for each library, I taught the LLMs about &lt;a href="https://clojure.github.io/clojure/clojure.repl-api.html"&gt;clojure.repl&lt;/a&gt;. This allows the LLM to dynamically explore any Clojure library on the REPL. Individual skills or lessons became less important, and the platform itself served as a dynamic feedback loop. Here is an example of that&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;discovering-functions&amp;gt;
The REPL is your documentation browser. Explore before coding:

List all public functions in a namespace:
```shell
clj-nrepl-eval -p 7889 "(clojure.repl/dir clojure.string)"
# Output: blank? capitalize ends-with? escape includes? index-of join
#         lower-case replace reverse split split-lines starts-with? trim ...
```

Get detailed documentation for any function:
```shell
clj-nrepl-eval -p 7889 "(clojure.repl/doc map)"
# Output:
# -------------------------
# clojure.core/map
# ([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 &amp;amp; colls])
#   Returns a lazy sequence consisting of the result of applying f to
#   the set of first items of each coll, followed by applying f to the
#   set of second items in each coll, until any one of the colls is
#   exhausted. Any remaining items in other colls are ignored...
```

Search for functions by name pattern:
```shell
clj-nrepl-eval -p 7889 "(clojure.repl/apropos \"split\")"
# Output: (clojure.string/split clojure.string/split-lines split-at split-with)
```

Search documentation text:
```shell
clj-nrepl-eval -p 7889 "(clojure.repl/find-doc \"regular expression\")"
# Shows all functions whose documentation mentions "regular expression"
```

Read function source code:
```shell
clj-nrepl-eval -p 7889 "(clojure.repl/source filter)"
# Shows the actual implementation - great for understanding edge cases
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Despite these breakthroughs, the development flow was still not what I wanted it to be. First, &lt;a href="https://opencode.ai/"&gt;OpenCode&lt;/a&gt; seems to consume a large amount of memory. It is a real bummer when you're working with a context window that you have crafted over thirty or forty minutes, and the LLM agent crashes because of the harness. It's a very frustrating experience.&lt;/p&gt;
&lt;p&gt;Additionally, I've noticed that &lt;a href="https://opencode.ai/"&gt;OpenCode&lt;/a&gt; tries to minimize the output of tool calls. This follows the general industry pattern we see in Claude Code, where certain information is hidden from the engineer. It's unclear to me what the exact design goals are, but I believe they're trying to minimize the information the developer sees so as not to overwhelm them.&lt;/p&gt;
&lt;p&gt;However, when you're refining your system prompts and thinking carefully about your LLM interactions, this hiding of information becomes a real hindrance. I want to know that my tool calls are correct, that my edits are clear, and that my system functions as effectively as possible.&lt;/p&gt;
&lt;p&gt;At this point I read &lt;a href="https://lucumr.pocoo.org/2026/1/31/pi/"&gt;a blog post&lt;/a&gt; about a new agent, &lt;a href="https://github.com/badlogic/pi-mono"&gt;pi-agent&lt;/a&gt;, and I was hooked.&lt;/p&gt;
&lt;h2 id="pi-agent-and-liberation"&gt;PI Agent and liberation&lt;/h2&gt;&lt;p&gt;Coming from an LLM agent harness like Claude Code that attempts to hide what the LLM is doing to a simple LLM harness that shows you everything felt like a liberating experience to me. Not only that, but &lt;a href="https://github.com/badlogic/pi-mono"&gt;pi-agent&lt;/a&gt; encourages you to solve your own problems, just like Emacs does. If there's a tool or functionality that you need that &lt;a href="https://github.com/badlogic/pi-mono"&gt;pi-agent&lt;/a&gt; doesn't provide, you simply build your own plugin. I have written a couple of plugins and used several more from the community.&lt;/p&gt;
&lt;p&gt;One plugin I wrote is something to track my energy usage while working with LLMs. Like many, I am deeply concerned about LLMs' effects on our planet. My &lt;a href="https://github.com/iwillig/dotfile/blob/main/pi/.pi/extensions/carbon-tracker.ts"&gt;plugin tracks&lt;/a&gt; how much carbon my interactions are generating and compares that to standard car use.&lt;/p&gt;
&lt;p&gt;What I value most about &lt;a href="https://github.com/badlogic/pi-mono"&gt;pi-agent&lt;/a&gt; is its reliability over long contexts. It also clearly indicates whether a tool call succeeded or failed with a green or red output. It's very minimal, does not use sub-agents, and allows me to focus on what my LLM is doing. As I follow along, I can tighten my iteration loop even more.&lt;/p&gt;
&lt;h2 id="current-stack-and-future-directions"&gt;Current stack and future directions&lt;/h2&gt;&lt;p&gt;My current stack is &lt;a href="https://github.com/bhauman/clojure-mcp-light"&gt;Clojure MCP Light&lt;/a&gt;, &lt;a href="https://github.com/badlogic/pi-mono"&gt;pi-agent&lt;/a&gt;, and my own &lt;a href="https://github.com/iwillig/clojure-system-prompt"&gt;system prompt&lt;/a&gt;. I plan to continue iterating on the Clojure system prompt and develop tools that make working with Clojure from LLMs more effective. I've also begun to experiment more and more with open-weight models, including the excellent &lt;a href="https://huggingface.co/moonshotai/Kimi-K2.5"&gt;Kimi 2.5 model&lt;/a&gt; from &lt;a href="https://www.moonshot.ai/"&gt;Moonshot AI&lt;/a&gt;, which is effectively my day-to-day driver now along with Sonnet 4.5.&lt;/p&gt;
&lt;p&gt;There are a bunch of future directions that I would love to explore if given the time and opportunity. The rise of very effective open-weight models like &lt;a href="https://huggingface.co/moonshotai/Kimi-K2.5"&gt;Kimi 2.5&lt;/a&gt; and &lt;a href="https://huggingface.co/MiniMaxAI/MiniMax-M2.5"&gt;MinMax 2.5&lt;/a&gt; opens the possibility to post-train these models on Clojure and on our code base. Theoretically, this could allow us to not even have to use custom system prompts, skills, or specialized tools. We could effectively train our own Kimi Clojure or MinMax Clojure model that would have all of our best practices baked in. Of course, this is theoretical; I've done a little research, and there seems to be strong evidence that this would work. However, we won't really know until we try it out.&lt;/p&gt;
&lt;p&gt;I also think it would be worthwhile to begin to curate all the tools that individual Clojure developers are building to work with Clojure and building an ecosystem and documentation around this.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;I think there is a tendency among certain users of LLMs to limit the platforms and languages that we use with the LLMs. Out of the box, there are good reasons to consider this. If I were working on a greenfield project, I might consider using something like Python. However, I believe software decisions should serve the needs of the people who work on it and with it, not the needs of the LLM that might play a role creating it.&lt;/p&gt;
&lt;p&gt;Ultimately, human beings are responsible for managing these software systems, and human knowledge still, in my view, trumps LLM statistical output. Taking that into consideration, the artifact produced by the LLM process is still a critical part of software development. I prefer to debug and deploy Clojure JVM artifacts, and I know many other organizations do as well.&lt;/p&gt;
&lt;p&gt;Also, I think there are important reasons not to abandon all the lessons we've learned over the last twenty years of using Clojure. Clojure itself eliminates a whole set of common engineering problems like statefulness. I don't see the advantage of going back to a language where we have to use mutable state for iteration, for example, or a language where functional idioms are not the default.&lt;/p&gt;
&lt;p&gt;My experiences with using an LLM for software engineering and developing an LLM have taught me that, rather than constraining the platforms we use and adopt, the LLM era could actually free us. The idea that LLMs are only good at something because it's in the training set ignores the tools, skills, and system prompts that we can apply post-training.&lt;/p&gt;
&lt;p&gt;We can craft and refine LLMs for our specific platform. Not only that, there is something beautiful about LLMs: instead of narrowing how we work, they can allow people to work in very different ways.&lt;/p&gt;
&lt;p&gt;I hope this blog post was insightful and informative, and I hope that my experience and our experience at &lt;a href="https://www.shortcut.com/"&gt;Shortcut&lt;/a&gt; can help you craft and shape an LLM experience that solves your particular engineering problems. Thank you for reading.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;&lt;p&gt;Katzy, J., &amp;amp; Izadi, M. (2023). On the Impact of Language Selection for Training and Evaluating Programming Language Models. &lt;em&gt;arXiv preprint arXiv:2308.13354&lt;/em&gt;. &lt;a href="https://arxiv.org/abs/2308.13354"&gt;https://arxiv.org/abs/2308.13354&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., &amp;amp; Liang, P. (2023). Lost in the Middle: How Language Models Use Long Contexts. &lt;em&gt;arXiv preprint arXiv:2307.03172&lt;/em&gt;. &lt;a href="https://arxiv.org/abs/2307.03172"&gt;https://arxiv.org/abs/2307.03172&lt;/a&gt;&lt;/p&gt;
</content></entry><entry><title>On Dyslexia, Programming and Lisp.</title><link href="https://iwillig.github.io/blog/on-dyslexia-and-lisp/" rel="alternate"/><updated>2026-02-10T00:00:00Z</updated><author><name>Ivan Willig</name></author><id>urn:uuid:564bb8f0-7364-3ce0-bd7f-cc9d0ce3a25e</id><content type="html">&lt;p&gt;Dyslexia is a difference in the way the brain processes language. It is often defined as the difference between intelligence (vaguely defined via the &lt;a href="https://en.wikipedia.org/wiki/The_Mismeasure_of_Man"&gt;fake IQ&lt;/a&gt; test) and a person's ability to read. That is, a dyslexic with normal intelligence will underperform compared to their peers in learning how to read. There are many different theories on what the cause is, but most people believe that there are fundamental differences between the brains of people who have dyslexia and those that do not. In Maryanne Wolf's book, &lt;a href="https://www.goodreads.com/book/show/6090306-dyslexia-fluency-and-the-brain"&gt;Dyslexia, Fluency, and the Brain&lt;/a&gt;, several essays point to a physical difference in the density of specialized brain cells that the brain uses to process letter shapes. It has become clear recently from fMRIs that dyslexics actually develop unique neural pathways for reading that are located in the right side of the brain compared to the normal left-side-dominated reading pathways. However, a root cause has not been identified, and the jury is still out. Dyslexia varies considerably, with up to 15% of the population experiencing some form of it.&lt;/p&gt;
&lt;p&gt;The experience of dyslexia is very interesting. I am dyslexic, and I was diagnosed in the 3rd grade. My dyslexia was more "intense" than some, having both verbal and visual effects. I remember trying to learn to read and struggling with all the letter rotation on the page. PQ DB IL letters all looked the same to me.&lt;/p&gt;
&lt;p&gt;The best way to describe it is that my brain was attempting to process these letters for their meaning and would automatically rotate the letter. When you think about it, it actually makes sense. When you pick up an object with your hand that you don't understand, what do most people do? They turn it to look at all sides of the object. That's what my brain does with letters.&lt;/p&gt;
&lt;p&gt;Because English letters are not "fixed," there is no inherent difference between the bottom and top of the letter; my brain could not fix the position of the letter on the page. The rotation was intense for me, as I was experiencing rotation in "3D." For me, this made the letter move more on the page and also made the letters even more confusing for me.&lt;/p&gt;
&lt;p&gt;It made reading very difficult until my brain was able to fix their position. I did this by adopting what I called the "Sky line" approach. Each word makes a unique shape, like the skyline of buildings in a city. By memorizing the shape of each word in the English language, I could fix the position of the word on the page and overcome this rotation problem. Interestingly enough, once I memorized the shape of most of the words in English, I found this method to be very fast. It may be because I can see the shape of the word quickly, not relying on letter shape.&lt;/p&gt;
&lt;p&gt;Frankly, rotation was a nightmare for me, literally. As a child, I remember having dreams/nightmares where I was falling and spinning. I would fall forever, spinning and spinning. I could not orient myself. Once, in this nightmare, I realized my solution. There was no fixed position. My desire to fix the position of the letter, or myself, was a fool's errand. Rotation itself was the only constant; the rotation, in a way, was a fixed position. It's a little difficult to describe, and this "realization" did come to me as a dream, so take it for what you will. I guess I am still spinning.&lt;/p&gt;
&lt;p&gt;Anyway, moving on from that, in college I developed an interest in software engineering. Taking a class in C and then quickly learning Python. C and its family of languages are interesting. They are frankly a nightmare of obscure scribbles that had little meaning to me. I think a classic example of this is the ternary conditional operator. This is an operator that I still struggle with. I  rapidly found that Python's whitespace-based syntax was much easier on my spinning brain. Most of the obscure squiggles are removed, and I was able to focus on the semantic meaning of the program. I found that my Sky Line approach for visual memorization works very well for programming languages. Each function or class defines its own shape. Remembering each shape in a large codebase is easier than remembering how to spell my own name.&lt;/p&gt;
&lt;p&gt;After college I learned about Lisp, specifically the Clojure programming language. After learning about Clojure, I dove headlong into Scheme and its family of S-expression languages. At last, I had found a language that was seemingly designed for my spinning brain.&lt;/p&gt;
&lt;p&gt;Lisp-based languages use S-expressions. These S-expressions form a constant and regular pattern.  All expressions are enclosed in parentheses (). The first symbol is always the function or the macro. The rest of the expressions are arguments to that function or macro. Lisp programming is layers and layers of S-expressions, each with their own shape, making them easy to memorize and locate. I no longer have to worry about the rotation of the ternary conditional operator.&lt;/p&gt;
&lt;p&gt;Scheme fascinates me even further. Many of the coding conventions in Scheme encourage people to use full and descriptive function names. I believe this gave my mind a deeper ability to organize the skyline shapes. Maybe you can think of this like the difference between 32-bit and 64-bit pointers in garbage collectors. The longer the function name, the more functions I can store in my mind's working memory.&lt;/p&gt;
&lt;p&gt;These experiences leave me wondering what other characteristics we could add to our programming languages that would help people like myself. In general, people with "normal," left-dominated reading pathways have constructed languages. In doing so, they have built language that works effectively for them. These languages are tangles of obscure squiggles, a spinning brain nightmare. This tangle allows for denser packing of meaning into a "smaller" space, easy for normies, or so they think. But I can't help but notice the popularity of languages like Ruby and Python. Both of these languages take approaches that reduce the visual complexity.&lt;/p&gt;
&lt;p&gt;I wonder if by reducing the visual complexity, we can instead increase the amount of semantic complexity we can hold in our minds. Dyslexia is common, and many people experience some form of the issues I describe above. Perhaps, we are all suffering from the tyranny of obscure squiggles.&lt;/p&gt;
</content></entry><entry><title>Use pandoc to create engineering-focused presentations.</title><link href="https://iwillig.github.io/blog/use-pandoc-to-create-engineering-focused-presentations/" rel="alternate"/><updated>2026-02-06T00:00:00Z</updated><author><name>Ivan Willig</name></author><id>urn:uuid:9b552e3d-4bda-3c2f-a0dc-a2e77736d256</id><content type="html">&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;&lt;p&gt;The blog post describes how I use &lt;a href="https://pandoc.org/"&gt;pandoc&lt;/a&gt; and &lt;a href="https://www.w3.org/Talks/Tools/Slidy2/"&gt;Slidy&lt;/a&gt; WC3 to build engineering focused presentations.&lt;/p&gt;
&lt;p&gt;Software engineering requires a lot of communication with stakeholders, your colleagues, and to LLM agents. There are many ways you can build a presentation these days, but I often find myself using &lt;a href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt;'s presentation mode. &lt;a href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt; allows me to write a presentation as a simple markdown file. This allows me to commit the presentation to our code repo. It also makes it easier to work with an LLM agent in building or editing your presentation.&lt;/p&gt;
&lt;p&gt;I have also used &lt;a href="https://pandoc.org/"&gt;pandoc&lt;/a&gt; and its presentation system when working with agents. After I work with the LLM agent, I will often ask it to create a presentation for me using markdown and link to the code. This then allows me to review what the LLM Agent built. It also allows me to share the implementation with my colleagues. Okay, without further ado, let's build our presentation.&lt;/p&gt;
&lt;p&gt;We are going to use &lt;a href="https://pandoc.org/"&gt;pandoc&lt;/a&gt;, &lt;a href="https://github.com/casey/just"&gt;just&lt;/a&gt;, and &lt;a href="https://pipenv.pypa.io/"&gt;pipenv&lt;/a&gt; to build our presentation with PlantUML diagram support.&lt;/p&gt;
&lt;h2 id="install"&gt;Install&lt;/h2&gt;&lt;p&gt;First, install the required tools:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;just&lt;span class="w"&gt; &lt;/span&gt;pipenv
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, create a &lt;code&gt;Pipfile&lt;/code&gt; in your project directory to manage Python dependencies:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;[[source]]&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://pypi.org/simple&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;verify_ssl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pypi&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[packages]&lt;/span&gt;
&lt;span class="n"&gt;pandoc-plantuml-filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[dev-packages]&lt;/span&gt;

&lt;span class="k"&gt;[requires]&lt;/span&gt;
&lt;span class="n"&gt;python_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;3&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Install the Python dependencies:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="create-your-presentation"&gt;Create Your Presentation&lt;/h2&gt;&lt;p&gt;Now let's create a basic presentation document. Save this as &lt;code&gt;presentation.md&lt;/code&gt;:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;% Engineering Presentation
% Your Name
% 2026-02-06

&lt;span class="gh"&gt;# Project Overview&lt;/span&gt;

&lt;span class="gu"&gt;## Goals&lt;/span&gt;

&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Improve system reliability
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Reduce latency by 50%
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Better observability

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;

&lt;span class="gh"&gt;# Implementation Details&lt;/span&gt;

&lt;span class="gu"&gt;## Key Changes&lt;/span&gt;

&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Added caching layer with Redis
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Implemented circuit breaker pattern
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Enhanced logging with structured JSON

&lt;span class="gu"&gt;## Code Example&lt;/span&gt;

Here&amp;#39;s how we implemented the circuit breaker:

&lt;span class="sb"&gt;```python&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;closed&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;open&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Circuit open&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;open&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## System Architecture&lt;/span&gt;

&lt;span class="sb"&gt;```plantuml&lt;/span&gt;
&lt;span class="s"&gt;@startuml&lt;/span&gt;
&lt;span class="s"&gt;!theme plain&lt;/span&gt;

&lt;span class="s"&gt;package &amp;quot;Client Layer&amp;quot; {&lt;/span&gt;
&lt;span class="s"&gt;  [Web App]&lt;/span&gt;
&lt;span class="s"&gt;  [Mobile App]&lt;/span&gt;
&lt;span class="s"&gt;}&lt;/span&gt;

&lt;span class="s"&gt;package &amp;quot;API Layer&amp;quot; {&lt;/span&gt;
&lt;span class="s"&gt;  [Load Balancer]&lt;/span&gt;
&lt;span class="s"&gt;  [API Gateway]&lt;/span&gt;
&lt;span class="s"&gt;  [Circuit Breaker]&lt;/span&gt;
&lt;span class="s"&gt;}&lt;/span&gt;

&lt;span class="s"&gt;package &amp;quot;Service Layer&amp;quot; {&lt;/span&gt;
&lt;span class="s"&gt;  [Auth Service]&lt;/span&gt;
&lt;span class="s"&gt;  [Cache Service]&lt;/span&gt;
&lt;span class="s"&gt;  [Core Service]&lt;/span&gt;
&lt;span class="s"&gt;}&lt;/span&gt;

&lt;span class="s"&gt;database &amp;quot;Data Layer&amp;quot; {&lt;/span&gt;
&lt;span class="s"&gt;  [Redis Cache]&lt;/span&gt;
&lt;span class="s"&gt;  [PostgreSQL]&lt;/span&gt;
&lt;span class="s"&gt;}&lt;/span&gt;

&lt;span class="s"&gt;[Web App] --&amp;gt; [Load Balancer]&lt;/span&gt;
&lt;span class="s"&gt;[Mobile App] --&amp;gt; [Load Balancer]&lt;/span&gt;
&lt;span class="s"&gt;[Load Balancer] --&amp;gt; [API Gateway]&lt;/span&gt;
&lt;span class="s"&gt;[API Gateway] --&amp;gt; [Circuit Breaker]&lt;/span&gt;
&lt;span class="s"&gt;[Circuit Breaker] --&amp;gt; [Auth Service]&lt;/span&gt;
&lt;span class="s"&gt;[Circuit Breaker] --&amp;gt; [Core Service]&lt;/span&gt;
&lt;span class="s"&gt;[Core Service] --&amp;gt; [Cache Service]&lt;/span&gt;
&lt;span class="s"&gt;[Cache Service] --&amp;gt; [Redis Cache]&lt;/span&gt;
&lt;span class="s"&gt;[Core Service] --&amp;gt; [PostgreSQL]&lt;/span&gt;

&lt;span class="s"&gt;@enduml&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## Metrics&lt;/span&gt;

| Metric      | Before | After | Improvement |
|-------------|--------|-------|-------------|
| Latency p99 | 250ms  | 120ms | 52% faster  |
| Error rate  | 0.5%   | 0.05% | 10x better  |
| Cache hit   | 0%     | 85%   | New         |

&lt;span class="gu"&gt;## Next Steps&lt;/span&gt;

&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Roll out to production
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Monitor for 1 week
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Document runbooks
&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="building-your-presentation"&gt;Building Your Presentation&lt;/h2&gt;&lt;p&gt;Now let's create a &lt;code&gt;justfile&lt;/code&gt; to automate building our presentation with PlantUML support:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Default recipe - build the presentation&lt;/span&gt;
&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;

&lt;span class="c"&gt;# Build the HTML presentation using Slidy with PlantUML support&lt;/span&gt;
&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;slidy&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;--filter&lt;span class="w"&gt; &lt;/span&gt;pandoc-plantuml&lt;span class="w"&gt; &lt;/span&gt;presentation.md&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;presentation.html

&lt;span class="c"&gt;# Build a self-contained single-file presentation&lt;/span&gt;
&lt;span class="nf"&gt;build-standalone&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;slidy&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;--embed-resources&lt;span class="w"&gt; &lt;/span&gt;--standalone&lt;span class="w"&gt; &lt;/span&gt;--filter&lt;span class="w"&gt; &lt;/span&gt;pandoc-plantuml&lt;span class="w"&gt; &lt;/span&gt;presentation.md&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;presentation.html

&lt;span class="c"&gt;# Clean generated files&lt;/span&gt;
&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;presentation.html
&lt;span class="w"&gt;    &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;plantuml-images

&lt;span class="c"&gt;# Watch for changes and rebuild&lt;/span&gt;
&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;just&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fswatch&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;presentation.md&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xargs&lt;span class="w"&gt; &lt;/span&gt;-n1&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="o"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;just&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now let's build our new presentation:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;just&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This generates &lt;code&gt;presentation.html&lt;/code&gt; which you can open in any browser. Use the arrow keys to navigate between slides. The &lt;a href="https://www.w3.org/Talks/Tools/Slidy2/"&gt;Slidy&lt;/a&gt; format provides a clean, professional look that's perfect for engineering presentations. The PlantUML diagrams will be automatically rendered as SVG images inline in your presentation.&lt;/p&gt;
</content></entry><entry><title>New Blog Setup</title><link href="https://iwillig.github.io/blog/new-blog-setup/" rel="alternate"/><updated>2026-02-02T00:00:00Z</updated><author><name>Ivan Willig</name></author><id>urn:uuid:3f6caf3b-3186-37dc-9c6b-99acd70a0e3d</id><content type="html">&lt;p&gt;I've migrated this blog to a new setup. Here's how it works now:&lt;/p&gt;
&lt;h2 id="stack"&gt;Stack&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.getlektor.com/"&gt;Lektor&lt;/a&gt;&lt;/strong&gt; - Static site generator with a nice content model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://pandoc.org/"&gt;Pandoc&lt;/a&gt;&lt;/strong&gt; - Builds my resume from YAML to HTML&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/casey/just"&gt;just&lt;/a&gt;&lt;/strong&gt; - Command runner for build tasks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;&lt;/strong&gt; - Automatic deployment on push to main&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://purecss.io/"&gt;PureCSS&lt;/a&gt;&lt;/strong&gt; - Lightweight CSS framework&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-it-works"&gt;How It Works&lt;/h2&gt;&lt;p&gt;Content lives in YAML and Lektor's &lt;code&gt;.lr&lt;/code&gt; files. The build process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;just resume&lt;/code&gt; - Pandoc converts &lt;code&gt;src/resume.yml&lt;/code&gt; to HTML using a custom template&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lektor build&lt;/code&gt; - Lektor builds the rest of the site&lt;/li&gt;
&lt;li&gt;GitHub Actions deploys to GitHub Pages on every push&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="why-lektor"&gt;Why Lektor?&lt;/h2&gt;&lt;p&gt;I really enjoyed using GNU Guix and Haunt. Guix is a fantastic package manager with reproducible builds, and Haunt is a lovely Scheme-based static site generator. However, I wanted something that runs easily on macOS without needing a Linux VM or container.&lt;/p&gt;
&lt;p&gt;More importantly, I wanted to focus on writing blog posts, not building and maintaining my own blogging system. Lektor has a clean content model, and just works out of the box.&lt;/p&gt;
&lt;h2 id="plantuml-diagrams"&gt;PlantUML Diagrams&lt;/h2&gt;&lt;p&gt;I wrote a small Lektor plugin that renders &lt;a href="https://plantuml.com/"&gt;PlantUML&lt;/a&gt; diagrams directly in markdown. Just use a fenced code block with &lt;code&gt;plantuml&lt;/code&gt; as the language:&lt;/p&gt;
&lt;div class="plantuml-diagram"&gt;&lt;?plantuml 1.2026.2beta3?&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="SEQUENCE" height="298px" preserveAspectRatio="none" style="width:536px;height:298px;background:#FFFFFF;" version="1.1" viewBox="0 0 536 298" width="536px" zoomAndPan="magnify"&gt;&lt;defs/&gt;&lt;g&gt;&lt;g class="participant-lifeline" data-entity-uid="part1" data-qualified-name="User" id="part1-lifeline"&gt;&lt;g&gt;&lt;title&gt;User&lt;/title&gt;&lt;rect fill="#000000" fill-opacity="0.00000" height="136.5313" width="8" x="19.9551" y="81.2969"/&gt;&lt;line style="stroke:#181818;stroke-width:0.5;stroke-dasharray:5,5;" x1="23" x2="23" y1="81.2969" y2="217.8281"/&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class="participant-lifeline" data-entity-uid="part2" data-qualified-name="L" id="part2-lifeline"&gt;&lt;g&gt;&lt;title&gt;Lektor&lt;/title&gt;&lt;rect fill="#000000" fill-opacity="0.00000" height="136.5313" width="8" x="286.772" y="81.2969"/&gt;&lt;line style="stroke:#181818;stroke-width:0.5;stroke-dasharray:5,5;" x1="290.6064" x2="290.6064" y1="81.2969" y2="217.8281"/&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class="participant-lifeline" data-entity-uid="part3" data-qualified-name="P" id="part3-lifeline"&gt;&lt;g&gt;&lt;title&gt;PlantUML Server&lt;/title&gt;&lt;rect fill="#000000" fill-opacity="0.00000" height="136.5313" width="8" x="461.6748" y="81.2969"/&gt;&lt;line style="stroke:#181818;stroke-width:0.5;stroke-dasharray:5,5;" x1="464.7949" x2="464.7949" y1="81.2969" y2="217.8281"/&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class="participant participant-head" data-entity-uid="part1" data-qualified-name="User" id="part1-head"&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="31.9102" x="5" y="77.9951"&gt;User&lt;/text&gt;&lt;ellipse cx="23.9551" cy="13.5" fill="#E2E2F0" rx="8" ry="8" style="stroke:#181818;stroke-width:0.5;"/&gt;&lt;path d="M23.9551,21.5 L23.9551,48.5 M10.9551,29.5 L36.9551,29.5 M23.9551,48.5 L10.9551,63.5 M23.9551,48.5 L36.9551,63.5" fill="none" style="stroke:#181818;stroke-width:0.5;"/&gt;&lt;/g&gt;&lt;g class="participant participant-tail" data-entity-uid="part1" data-qualified-name="User" id="part1-tail"&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="31.9102" x="5" y="229.8232"&gt;User&lt;/text&gt;&lt;ellipse cx="23.9551" cy="241.625" fill="#E2E2F0" rx="8" ry="8" style="stroke:#181818;stroke-width:0.5;"/&gt;&lt;path d="M23.9551,249.625 L23.9551,276.625 M10.9551,257.625 L36.9551,257.625 M23.9551,276.625 L10.9551,291.625 M23.9551,276.625 L36.9551,291.625" fill="none" style="stroke:#181818;stroke-width:0.5;"/&gt;&lt;/g&gt;&lt;g class="participant participant-head" data-entity-uid="part2" data-qualified-name="L" id="part2-head"&gt;&lt;rect fill="#E2E2F0" height="30.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="58.3311" x="261.6064" y="50"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="44.3311" x="268.6064" y="69.9951"&gt;Lektor&lt;/text&gt;&lt;/g&gt;&lt;g class="participant participant-tail" data-entity-uid="part2" data-qualified-name="L" id="part2-tail"&gt;&lt;rect fill="#E2E2F0" height="30.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="58.3311" x="261.6064" y="216.8281"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="44.3311" x="268.6064" y="236.8232"&gt;Lektor&lt;/text&gt;&lt;/g&gt;&lt;g class="participant participant-head" data-entity-uid="part3" data-qualified-name="P" id="part3-head"&gt;&lt;rect fill="#E2E2F0" height="30.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="129.7598" x="400.7949" y="50"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="115.7598" x="407.7949" y="69.9951"&gt;PlantUML Server&lt;/text&gt;&lt;/g&gt;&lt;g class="participant participant-tail" data-entity-uid="part3" data-qualified-name="P" id="part3-tail"&gt;&lt;rect fill="#E2E2F0" height="30.2969" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="129.7598" x="400.7949" y="216.8281"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="115.7598" x="407.7949" y="236.8232"&gt;PlantUML Server&lt;/text&gt;&lt;/g&gt;&lt;g class="message" data-entity-1="part1" data-entity-2="part2" id="msg1"&gt;&lt;polygon fill="#181818" points="278.772,108.4297,288.772,112.4297,278.772,116.4297,282.772,112.4297" style="stroke:#181818;stroke-width:1;"/&gt;&lt;line style="stroke:#181818;stroke-width:1;" x1="23.9551" x2="284.772" y1="112.4297" y2="112.4297"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="242.8169" x="30.9551" y="107.3638"&gt;Write markdown with PlantUML block&lt;/text&gt;&lt;/g&gt;&lt;g class="message" data-entity-1="part2" data-entity-2="part3" id="msg2"&gt;&lt;polygon fill="#181818" points="453.6748,137.5625,463.6748,141.5625,453.6748,145.5625,457.6748,141.5625" style="stroke:#181818;stroke-width:1;"/&gt;&lt;line style="stroke:#181818;stroke-width:1;" x1="290.772" x2="459.6748" y1="141.5625" y2="141.5625"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="150.9028" x="297.772" y="136.4966"&gt;Send encoded diagram&lt;/text&gt;&lt;/g&gt;&lt;g class="message" data-entity-1="part3" data-entity-2="part2" id="msg3"&gt;&lt;polygon fill="#181818" points="301.772,166.6953,291.772,170.6953,301.772,174.6953,297.772,170.6953" style="stroke:#181818;stroke-width:1;"/&gt;&lt;line style="stroke:#181818;stroke-width:1;stroke-dasharray:2,2;" x1="295.772" x2="464.6748" y1="170.6953" y2="170.6953"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="75.3022" x="307.772" y="165.6294"&gt;Return SVG&lt;/text&gt;&lt;/g&gt;&lt;g class="message" data-entity-1="part2" data-entity-2="part1" id="msg4"&gt;&lt;polygon fill="#181818" points="34.9551,195.8281,24.9551,199.8281,34.9551,203.8281,30.9551,199.8281" style="stroke:#181818;stroke-width:1;"/&gt;&lt;line style="stroke:#181818;stroke-width:1;stroke-dasharray:2,2;" x1="28.9551" x2="289.772" y1="199.8281" y2="199.8281"/&gt;&lt;text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="229.0615" x="40.9551" y="194.7622"&gt;Rendered page with inline diagram&lt;/text&gt;&lt;/g&gt;&lt;?plantuml-src LOt12i8m44Jl-OgXz_v03rwzH0YMwdbDbXhQRiessjyteIXuMfYPzvPyJefXOpKhQOu-hYGPZUMf50reW_rlt5pESB7eMTzyJ9mnLO7s1DlXhZ4p5j8ff5smn_p4ZthCoK_6rgdhYaC2M7mA710YZKgBSMWFqPNpImNzxLo1ajKdDPR0Me2LHZxqKUOe_3Ly0000?&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;The plugin encodes the diagram text and fetches the rendered SVG from the public PlantUML server at build time. The SVG is embedded inline, so no external requests are made when viewing the page.&lt;/p&gt;
&lt;p&gt;The full source is on &lt;a href="https://github.com/iwillig/iwillig.github.io"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content></entry><entry><title>Using Pandoc to Build a Technical Manual</title><link href="https://iwillig.github.io/blog/using-pandoc/" rel="alternate"/><updated>2025-01-04T00:00:00Z</updated><author><name>Ivan Willig</name></author><id>urn:uuid:69201b27-d379-313e-9b6e-c51b8d07a222</id><content type="html">&lt;p&gt;&lt;a href="https://pandoc.org/index.html"&gt;Pandoc&lt;/a&gt; is a markup transformation
tool that converts documents between formats. It's an invaluable tool
for any software engineer who works with documentation.&lt;/p&gt;
&lt;p&gt;In this guide, we'll build a "technical manual" using Pandoc. Our
manual will be a collection of Markdown documents with
&lt;a href="https://plantuml.com/"&gt;PlantUML&lt;/a&gt; diagrams. We'll use Pandoc to
combine these documents into a single HTML page, rendering PlantUML
diagrams as SVG images. Along the way, we'll explore Pandoc filters
for rendering diagrams and including source code from your project.&lt;/p&gt;
&lt;p&gt;By the end of this guide, you'll have enough understanding of Pandoc
to build your own documentation system.&lt;/p&gt;
&lt;h4 id="installation"&gt;Installation&lt;/h4&gt;&lt;p&gt;You can install Pandoc on macOS with Homebrew:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pandoc
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On Fedora:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dnf&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pandoc
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or with GNU Guix:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;guix&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pandoc
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="introduction-to-pandoc"&gt;Introduction to Pandoc&lt;/h4&gt;&lt;p&gt;Let's say you have a simple HTML document:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;description&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Software Wanderings&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;one item&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;two item&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;three item&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Relative reference&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;


  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Company&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Contact&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Country&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Alfreds Futterkiste&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Maria Anders&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Germany&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Centro comercial Moctezuma&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Francisco Chang&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Mexico&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0 0 100 100&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;preserveAspectRatio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;xMidYMid slice&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;img&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;A gradient&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;linearGradient&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gradient&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;stop&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0%&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;stop-color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;red&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;stop&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;end&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100%&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;stop-color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;black&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;linearGradient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;rect&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill:url(#gradient)&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;circle&lt;/span&gt; &lt;span class="na"&gt;cx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;50&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;cy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;50&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;30&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill:url(#gradient)&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;


&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can convert this from HTML to Markdown with the following
command:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;html&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;markdown&lt;span class="w"&gt; &lt;/span&gt;public/about.html
cat&lt;span class="w"&gt; &lt;/span&gt;public/about.html&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;html&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;markdown
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which produces the following output&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;# Hello&lt;/span&gt;

&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;  one item
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;  two item
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;  three item

[&lt;span class="nt"&gt;Relative reference&lt;/span&gt;](&lt;span class="na"&gt;/&lt;/span&gt;)

  Company                      Contact           Country
  ---------------------------- ----------------- ---------
  Alfreds Futterkiste          Maria Anders      Germany
  Centro comercial Moctezuma   Francisco Chang   Mexico

![](data:image/svg+xml;base64,PHN2ZyB2aWV3Ym94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVhc3BlY3RyYXRpbz0ieE1pZFlNaWQgc2xpY2UiIHJvbGU9ImltZyI+CiAgICA8dGl0bGU+QSBncmFkaWVudDwvdGl0bGU+CiAgICA8bGluZWFyZ3JhZGllbnQgaWQ9ImdyYWRpZW50Ij4KICAgICAgPHN0b3AgY2xhc3M9ImJlZ2luIiBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJyZWQiPjwvc3RvcD4KICAgICAgPHN0b3AgY2xhc3M9ImVuZCIgb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJibGFjayI+PC9zdG9wPgogICAgPC9saW5lYXJncmFkaWVudD4KICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBzdHlsZT0iZmlsbDp1cmwoI2dyYWRpZW50KSI+PC9yZWN0PgogICAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMzAiIHN0eWxlPSJmaWxsOnVybCgjZ3JhZGllbnQpIj48L2NpcmNsZT4KICA8L3N2Zz4=)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or Emacs's &lt;a href="https://orgmode.org/"&gt;Org Mode&lt;/a&gt;&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;* Hello&lt;/span&gt;
&lt;span class="nd"&gt;  :PROPERTIES:&lt;/span&gt;
&lt;span class="cs"&gt;  :CUSTOM_ID: hello&lt;/span&gt;
&lt;span class="nd"&gt;  :END:&lt;/span&gt;

&lt;span class="k"&gt;- &lt;/span&gt;one item
&lt;span class="k"&gt;- &lt;/span&gt;two item
&lt;span class="k"&gt;- &lt;/span&gt;three item

&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="na"&gt;/&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nt"&gt;Relative reference&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

&lt;span class="s"&gt;| Company                    | Contact         | Country |&lt;/span&gt;
&lt;span class="s"&gt;|----------------------------+-----------------+---------|&lt;/span&gt;
&lt;span class="s"&gt;| Alfreds Futterkiste        | Maria Anders    | Germany |&lt;/span&gt;
&lt;span class="s"&gt;| Centro comercial Moctezuma | Francisco Chang | Mexico  |&lt;/span&gt;

&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="na"&gt;data:image/svg+xml;base64,PHN2ZyB2aWV3Ym94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVhc3BlY3RyYXRpbz0ieE1pZFlNaWQgc2xpY2UiIHJvbGU9ImltZyI+CiAgICA8dGl0bGU+QSBncmFkaWVudDwvdGl0bGU+CiAgICA8bGluZWFyZ3JhZGllbnQgaWQ9ImdyYWRpZW50Ij4KICAgICAgPHN0b3AgY2xhc3M9ImJlZ2luIiBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJyZWQiPjwvc3RvcD4KICAgICAgPHN0b3AgY2xhc3M9ImVuZCIgb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJibGFjayI+PC9zdG9wPgogICAgPC9saW5lYXJncmFkaWVudD4KICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBzdHlsZT0iZmlsbDp1cmwoI2dyYWRpZW50KSI+PC9yZWN0PgogICAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMzAiIHN0eWxlPSJmaWxsOnVybCgjZ3JhZGllbnQpIj48L2NpcmNsZT4KICA8L3N2Zz4=&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pandoc also has native and JSON output formats. When you select
JSON, the raw AST that Pandoc generates from your document is
returned. Let's take a quick peek at this AST:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;code-examples/sample.html&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;html&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;code-examples/sample.json
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;t&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;BulletList&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;t&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Plain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;t&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Str&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;one&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;t&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Space&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;t&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Str&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;item&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can clearly see that Pandoc is a transformation system. It parses a
document into its native AST, then uses a set of writers to render
that AST into the target format:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INPUT --reader--&amp;gt; AST --filter--&amp;gt; AST --writer--&amp;gt; OUTPUT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Taken from the &lt;a href="https://pandoc.org/filters.html"&gt;Pandoc Website&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="building-your-technical-document"&gt;Building Your Technical Document&lt;/h4&gt;&lt;p&gt;Now that we understand the basics of how Pandoc works, let's use it to
build a technical manual. We have three main goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PlantUML-based diagrams&lt;/strong&gt; - Render architecture and sequence diagrams&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code inclusion&lt;/strong&gt; - Include code fragments from your project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown-based content&lt;/strong&gt; - Write in Markdown for simplicity&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="project-setup"&gt;Project Setup&lt;/h4&gt;&lt;p&gt;We'll use a separate Markdown file for each section and a
&lt;a href="https://github.com/casey/just"&gt;justfile&lt;/a&gt; to build our document into a
single HTML file. Just is a modern command runner that's simpler and
more intuitive than Make. Here's our project structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;justfile&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;metadata.yml&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sections/introduction.md&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sections/architecture.md&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, let's create a Pandoc metadata file. Pandoc uses YAML for
metadata, which you can include inline or in a separate file.&lt;/p&gt;
&lt;p&gt;Create a file called &lt;strong&gt;metadata.yml&lt;/strong&gt; with the following content:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Software&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;alchemist&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Docs&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Ivan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Willig&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;iwillig@gmail.com&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Software&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Alchemist&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Docs&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en-US&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;toc-title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Table&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Contents&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;chapters/introduction.org&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;chapters/design.org&lt;/span&gt;

&lt;span class="nt"&gt;css&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;css/pico.min.css&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;css/docs.css&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;css/highlighting.css&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let's create a &lt;code&gt;justfile&lt;/code&gt; to automate the build process. First, install Just:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# macOS&lt;/span&gt;
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;just

&lt;span class="c1"&gt;# Or with cargo&lt;/span&gt;
cargo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;just
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Create a file called &lt;strong&gt;justfile&lt;/strong&gt; with the following content:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Build the documentation&lt;/span&gt;
&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;

&lt;span class="c"&gt;# Generate HTML documentation from markdown sources&lt;/span&gt;
&lt;span class="nf"&gt;docs.html&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sections&lt;/span&gt;/*.&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;.&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;metadata.yml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--toc&lt;span class="w"&gt; &lt;/span&gt;--toc-depth&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--template&lt;span class="w"&gt; &lt;/span&gt;templates/template.html&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--highlight-style&lt;span class="w"&gt; &lt;/span&gt;zenburn&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-F&lt;span class="w"&gt; &lt;/span&gt;pandoc-plantuml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;markdown&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;html&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;sections/introduction.md&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;sections/architecture.md&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;docs.html

&lt;span class="c"&gt;# Clean generated files&lt;/span&gt;
&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;docs.html
&lt;span class="w"&gt;    &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;plantuml-images/*.svg
&lt;span class="w"&gt;    &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;plantuml-images/*.uml

&lt;span class="c"&gt;# Watch for changes and rebuild&lt;/span&gt;
&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;watchexec&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;md,yml&lt;span class="w"&gt; &lt;/span&gt;just
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now you can build your documentation by running:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;just
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or clean up generated files with:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;just&lt;span class="w"&gt; &lt;/span&gt;clean
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="using-pandoc-filters"&gt;Using Pandoc Filters&lt;/h4&gt;&lt;p&gt;Pandoc filters allow you to manipulate the AST between the reader and
writer stages. This is where the real power of Pandoc comes in—you can
extend it to handle custom markup or integrate with external tools.&lt;/p&gt;
&lt;p&gt;For our technical manual, we'll use the &lt;code&gt;pandoc-plantuml&lt;/code&gt; filter to
render PlantUML diagrams. Install it with:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pandoc-plantuml
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You'll also need PlantUML itself:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# macOS&lt;/span&gt;
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;plantuml

&lt;span class="c1"&gt;# Or download the JAR file&lt;/span&gt;
&lt;span class="c1"&gt;# https://plantuml.com/download&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now you can include PlantUML diagrams directly in your Markdown:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;## Architecture Diagram&lt;/span&gt;

&lt;span class="sb"&gt;```plantuml&lt;/span&gt;
&lt;span class="s"&gt;@startuml&lt;/span&gt;
&lt;span class="s"&gt;actor User&lt;/span&gt;
&lt;span class="s"&gt;participant &amp;quot;Web Server&amp;quot; as Web&lt;/span&gt;
&lt;span class="s"&gt;participant &amp;quot;Database&amp;quot; as DB&lt;/span&gt;

&lt;span class="s"&gt;User -&amp;gt; Web: HTTP Request&lt;/span&gt;
&lt;span class="s"&gt;Web -&amp;gt; DB: Query&lt;/span&gt;
&lt;span class="s"&gt;DB --&amp;gt; Web: Result&lt;/span&gt;
&lt;span class="s"&gt;Web --&amp;gt; User: HTTP Response&lt;/span&gt;
&lt;span class="s"&gt;@enduml&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The filter automatically converts these code blocks into SVG images
when you build your documentation.&lt;/p&gt;
&lt;p&gt;You can also write custom filters in Python. Here's a simple example
that converts emphasized text to uppercase:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandocfilters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;toJSONFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Emph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Str&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Emph&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;toJSONFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="pandoc-templates"&gt;Pandoc Templates&lt;/h4&gt;&lt;p&gt;Each output format has a default template. You can view a format's
template using Pandoc itself:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;html
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This outputs the default HTML template. You can read more about
&lt;a href="https://pandoc.org/chunkedhtml-demo/6.1-template-syntax.html"&gt;Pandoc's template syntax&lt;/a&gt;
in the official documentation.&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$lang$&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;xml:lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$lang$&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="err"&gt;)$&lt;/span&gt; &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$dir$&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="na"&gt;endif&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;generator&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pandoc&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;width=device-width, initial-scale=1.0, user-scalable=yes&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
$for(author-meta)$
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;author&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$author-meta$&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
$endfor$
$if(date-meta)$
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dcterms.date&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;$date-meta$&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
$endif$
&lt;span class="cm"&gt;&amp;lt;!-- ... rest of template ... --&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can customize this template to add your own CSS, JavaScript, or
HTML structure. Save your custom template and reference it with the
&lt;code&gt;--template&lt;/code&gt; flag as shown in the justfile above.&lt;/p&gt;
&lt;h4 id="getting-started-quickly"&gt;Getting Started Quickly&lt;/h4&gt;&lt;p&gt;If you want to skip the manual setup, I've created a cookiecutter
template with everything pre-configured:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/iwillig/pandoc-documenation-tool/"&gt;pandoc-documentation-tool&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This template includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pre-configured justfile with common tasks&lt;/li&gt;
&lt;li&gt;Sample metadata.yml&lt;/li&gt;
&lt;li&gt;Example markdown sections&lt;/li&gt;
&lt;li&gt;HTML template with responsive CSS&lt;/li&gt;
&lt;li&gt;PlantUML filter setup&lt;/li&gt;
&lt;li&gt;Code inclusion utilities&lt;/li&gt;
&lt;li&gt;Example diagrams and structure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use it to bootstrap a new documentation project:&lt;/p&gt;
&lt;div class="hll"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Install cookiecutter if you haven&amp;#39;t already&lt;/span&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cookiecutter

&lt;span class="c1"&gt;# Generate a new project from the template&lt;/span&gt;
cookiecutter&lt;span class="w"&gt; &lt;/span&gt;gh:iwillig/pandoc-documenation-tool
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The template will prompt you for a project name, author, and other
details, then generate a complete documentation project. Customize it
to fit your needs.&lt;/p&gt;
&lt;h4 id="conclusion"&gt;Conclusion&lt;/h4&gt;&lt;p&gt;Pandoc is a powerful tool for building technical documentation. By
combining Markdown, PlantUML, and Pandoc filters, you can create
maintainable, version-controlled documentation that lives alongside
your code. Using Just as a command runner keeps the build process
simple and reproducible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key takeaways:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pandoc converts between formats by parsing to an AST, then rendering&lt;/li&gt;
&lt;li&gt;Filters extend Pandoc's capabilities&lt;/li&gt;
&lt;li&gt;Templates customize the output format&lt;/li&gt;
&lt;li&gt;PlantUML integration enables version-controlled diagrams&lt;/li&gt;
&lt;li&gt;Just provides a simple, modern way to automate builds&lt;/li&gt;
&lt;li&gt;The cookiecutter template gives you a head start&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You now have everything you need to build your own technical manual.
Start with the cookiecutter template, or build your setup from scratch
using this guide. Happy documenting!&lt;/p&gt;
</content></entry><entry><title>Hello, World</title><link href="https://iwillig.github.io/blog/hello-world/" rel="alternate"/><updated>2024-12-28T00:00:00Z</updated><author><name>Ivan Willig</name></author><id>urn:uuid:b397fb40-1db0-3769-81fd-10d7435a83cb</id><content type="html">&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post is a bit out of date. I no longer use GNU Guix and Haunt for this blog. Instead, I've migrated to &lt;a href="https://www.getlektor.com/"&gt;Lektor&lt;/a&gt; as my static site generator, with Pandoc still handling resume generation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Welcome to Software Wanderings, a technical blog where I'll be documenting my experiences, learnings, and observations about software engineering. I hope this blog will encourage me to write about my experiences and share them with a broader audience.&lt;/p&gt;
&lt;p&gt;The blog will explore software design, functional programming, API design and more. While I primarily work in Clojure, I'll also cover JavaScript, TypeScript, Rust, Scheme, and SQL.&lt;/p&gt;
&lt;h2 id="2025-goals"&gt;2025 Goals&lt;/h2&gt;&lt;p&gt;I have five primary objectives for the year:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Game Development&lt;/strong&gt;: Learn to build real-time strategy games inspired by Command and Conquer and StarCraft&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Immutable Linux&lt;/strong&gt;: Study NixOS, GNU Guix, and Fedora Silverblue distributions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pandoc Mastery&lt;/strong&gt;: Understand document transformation systems&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Design&lt;/strong&gt;: Deepen my knowledge of REST and GraphQL architectures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical Writing&lt;/strong&gt;: Publish insights throughout the year&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="technical-architecture"&gt;Technical Architecture&lt;/h2&gt;&lt;p&gt;This blog's infrastructure uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Pages&lt;/strong&gt; for hosting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Haunt&lt;/strong&gt; (a static site generator) for template processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pandoc&lt;/strong&gt; for Markdown preprocessing with advanced diagram support&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GNU Make&lt;/strong&gt; for build orchestration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GNU Guix&lt;/strong&gt; for dependency management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The setup requires Pandoc, PlantUML, and Haunt, with a custom GNU Guix manifest handling dependencies.&lt;/p&gt;
</content></entry></feed>