Elektrine lite

← Feed

@david_chisnall@infosec.exchange

2026-09-25 07:50 UTC

I talk a lot about building software for user customisation. I wanted to share a bit about what I did with igk. This is a tool written for precisely one user (me). I wanted, for the CHERIoT book, to have a single source of semantic markup that could be used to generate typeset PDFs (for print and online, with different styling), ePub (XHTML plus some metadata) and web versions. I initially tried AsciiDoc but it made a lot of design choices that are the opposite of what I want. The design of igk is very simple. It has a tiny C++ core that builds a document tree. That tree use an XML-like document model. A document is a tree. Each node has zero or more unordered attributes (key-value pairs) and zero or more ordered children (text or other nodes). The C++ part has a parser that generates this tree from something that looks like LaTeX (and is more or less the same as as SILE’s cleaned-up TeX markup). Importantly, the generic tool is completely agnostic to semantics. It handles a tree, it doesn’t know or care what any of those nodes mean. The tool then runs tree-to-tree transform passes. These are written in Lua or C++ (in theory, they could be written in anything that C++ can wrap). There is no default list of passes. Each pass is provided as a command-line argument. Passes can run other passes, so you can simplify the command-line invocation by writing one pass that just invokes others to define your workflow. The passes that I use for PDF and ePub output are very different. HTML-like formats want most of the semantic markup to be preserved right to the end and then styled with CSS. PDF output wants some semantics preserved (e.g. section labels for generating a ToC in PDF metadata) but other things lowered to presentation markup. Because the tool doesn’t special case any node types, I can trivially add new semantic markup by just writing it and then write the passes that let me typeset it nicely later. This methodology is built around the idea of desire paths. I make it easy to modify how the tool works and then I learn how I (and, in theory, other people) use it, then I make that use easier. As I’ve written passes, I’ve ended up duplicating the same patterns in a few places, so I’ve added helpers. There are a few more I should add to make the structure easier. A bunch of passes are doing the kind of trivial transform that could be made much simpler with a template-based DSL. Oh, there’s one other extension mechanism. The tool can load shared objects that expose a function that’s called with access to a Lua context. These can register new passes or they can register helper functions for Lua. I use this to expose TreeSitter and libclang into passes. The book pulls in Doxygen-style doc comments with libclang for function references and also pulls in listings from buildable examples. The helpers with libclang / TreeSitter build trees with semantic markup for the identifiers in the listings, the Lua code generates the listings with captions and so on.

Replies (0)

No replies.