lindisfarner
Rust CLI tool that illuminates text and source code with ASCII art. Also has the option of vandalizing or entirely breaking code that calls or implements neural models.
What it does
lindisfarner can:
- Illuminate prose — wrap plain text into a framed manuscript page: a FIGlet drop-cap, rubricated words, marginal drolleries, pilcrows, a two-column codex.
- Illuminate & search code — render source with keywords rubricated and comments lifted into the margin as glosses; or point a Semgrep pattern or rules at a codebase and illuminate the findings.
- Magnificate your code base — search a code base to identify use of neural models, and either annotate the code with quotes from Magnifica Humanitas or break the code outright. This mode writes directly to disk and does not check whether you have committed anything to git. Do not use it on code you rely on — it will break it.
Install
Or build from source:
The code-search modes (--find, --scan, --magnifica) also need
Semgrep on your PATH:
&&
Quickstart
lindisfarner reads a file (or standard input) and writes the illuminated page to
the terminal — or to a file with --output.
# Illuminate a text file (the page fills your terminal width)
# From a pipe; force colour through it, then page with less -R
| |
# Save a plain, colourless page to a file
# See every option
Colour is automatic: on for a terminal, off when piped or written to a file, so
saved pages stay clean plain text. With no --width, the page fills the terminal
(falling back to 60 columns when piped). If you built from source, the binary is
at ./target/release/lindisfarner; cargo install puts it on your PATH.
Illuminating prose
| Manuscript element | lindisfarner adaptation |
|---|---|
| Illuminated initial / versal | A large FIGlet drop-cap; the opening lines flow down its side |
| Rubrication (red ink for key words) | --rubricate word,word highlights matching words |
| Gold leaf / chrysography | The initial, painted in the theme's accent colour |
| Decorative border & marginalia | --border styles, with an ❦ flourish on the ornate frame |
| Drolleries (marginal doodles) | --drolleries scatters small ASCII figures down the margin |
| Pilcrows | --pilcrows runs paragraphs together, marked by an inline ¶ |
| Two-column codex | --columns 2 sets the body in side-by-side columns |
| The page itself | A wrapped, framed text block at a chosen --width |
# Every paragraph gets its own initial, in a double frame
# Crimson theme, rubricate a few words, force colour through a pipe
|
# A justified two-column codex page with a rubricated incipit
# Run paragraphs together with red/blue alternating pilcrows
|
Drolleries
With --drolleries, small ASCII figures are scattered down the left margin,
separated from the text by a ruled line. They are placed at fixed intervals
independent of the paragraph structure, so the margin fills with figures whether
the text is one flowing block or many paragraphs. These imitate the original
drolleries found in the margins of illuminated manuscripts, which most often
depicted human-animal hybrid figures that reflected the wild imagination of
medieval monastics. The figures come from a fixed built-in repertoire
(src/drollery.rs): a hare, cat, owl, fish, mouse, snail, bird, and a vine
flourish. Selection is deterministic, so a given file always renders the same;
pass --seed N to reshuffle the figures. Add your own by editing drollery.rs.
Typeface
The illuminated initials default to a Fraktur capital. The font is embedded in the binary
(fonts/fraktur.flf), so nothing extra is needed at runtime. Use
--font standard for plain FIGlet block capitals instead.
Credits: the blackletter glyphs come from the FIGlet font Fraktur.flf by Philip Menke (1995), part of the freely distributable FIGlet font collection. The default block font is the standard FIGlet font (Glenn Chappell & Ian Chai).
Notes & limitations
- Line endings are normalised, so
\r\n(Windows) and\r(classic Mac) files split into paragraphs the same way as Unix text. - Minimum width.
--widthis clamped to a floor of 24 columns so the page stays readable. - Pilcrows vs. columns vs. drolleries compose: pilcrows flow the text into
one block,
--columnssets that block in a codex, and drolleries scatter down the outer margin independent of the paragraph count.
Illuminating code
Point lindisfarner at a source file and it switches to code mode: lines are kept verbatim (indentation and all), the language's keywords are rubricated in red, and comments are lifted out into the margin as glosses — the way a scribe set commentary beside scripture.
╭─────────────────────────────❦─────────────────────────────╮
│ ┊ the program entry point │
│ fn main() { ┊ │
│ let greeting = "hi"; ┊ a friendly word │
│ for i in 0..3 { ┊ loop a few times │
│ println!("{greeting}"); ┊ │
│ } ┊ │
│ } ┊ │
╰─────────────────────────────❦─────────────────────────────╯
Languages are matched by extension (Rust, Python, JavaScript/TypeScript, C/C++,
Go, shell), with a generic fallback for anything else. Detection is keyword- and
comment-based rather than a full parser, so keywords inside string literals may
also be reddened — light vandalism, as intended. --drolleries, --border, and
--theme all still apply.
Scribal errors
Real manuscripts are full of copying mistakes. --corrupt introduces the same
errata (letters transposed, dropped, doubled, or misread), purposefully
breaking the text (and, in code mode, the code):
Only letters are touched, so whitespace, punctuation, and line count survive: the
page keeps its shape while the words quietly fall apart. The errors are
deterministic — change --seed for a different careless monk.
Searching code
The --find and --scan modes use Semgrep (see Install) to locate
code and illuminate the matches as a commentary page: each match set in code
style (keywords rubricated), with a gloss in the margin.
Point --find at a file or directory with a Semgrep pattern:
╭─────────────────────────────────────────────────❦────────────────────────────────────────────────╮
│ let (code, gloss) = g.split("let x = 5; // a number", by_name("rust").unwrap(… ┊ src/code.rs:345 │
│ let rust = by_name("rust").unwrap(); ┊ src/code.rs:352 │
│ let out = rubricate("fn main() {", by_name("rust").unwrap(), &style); ┊ src/code.rs:370 │
│ let (body, w) = illuminate(src, by_name("rust").unwrap(), &style, 80); ┊ src/code.rs:379 │
╰─────────────────────────────────────────────────❦────────────────────────────────────────────────╯
The language is taken from --language or detected from the path. The same
glossed-page rendering is available to library users as render_glossed.
Rules with --scan
For more than a single pattern, point --scan at a Semgrep rule config
— a file or a directory of rules. The rules decide what to find and what to
say; each finding is glossed with its rule message, marked by severity (†
ERROR, ☞ WARNING, ❧ INFO):
# rules.yml
rules:
- id: no-unwrap
languages:
severity: WARNING
message: a careless unwrap() — handle the error
pattern: $X.unwrap()
╭───────────────────────────❦───────────────────────────╮
│ x.unwrap() ┊ ☞ a careless unwrap() — handle the error │
│ y.unwrap() ┊ ☞ a careless unwrap() — handle the error │
╰───────────────────────────❦───────────────────────────╯
--scan accepts a single rule file or a directory of them, in which case
every rule runs together. This repo ships a starter library in rules/
— scan the whole codebase against all of them with lindisfarner src/ --scan rules. Add your own by dropping more .yml files in there; each is an ordinary
Semgrep rule. Because it's Semgrep underneath, you can also point
--scan at its registry (e.g. --scan p/python).
The magnifica modes (an art project)
Point lindisfarner at a codebase and it finds where AI is used and answers
with the words of the encyclical Magnifica Humanitas of Pope Leo XIV, "On
Safeguarding the Human Person in the Time of Artificial Intelligence" — supplied
as a PDF in assets/, with the passages curated into
assets/quotes.txt (override with --quotes).
"AI is used" means three things:
- Hosted AI APIs (
rules/ai.yml) — the major SDKs and agent frameworks across Python, JS/TS, and Go, plus raw API calls by hostname. - The ML lifecycle (
rules/ml.yml) — training your own neural models: framework imports, data prep, loading, training, fine-tuning, evaluation, and deployment. - Model weights — the binary artifacts (
.safetensors,.pt,.onnx, …) found by walking the filesystem, since Semgrep reads only source code.
Two modes — both write the encyclical into the files on disk, then print an illuminated report of what was changed. Model weight files are reported but never modified. This mode does NOT check to see if you have committed anything to your git working tree. Engage with caution!
-
witness— insert the encyclical as a comment beside every AI invocation, leaving the code that runs intact.# In one sense, technological innovation can represent human # participation in the divine act of creation. — Magnifica Humanitas §111The report then names each annotated location and reads the encyclical beneath.
-
relinquish— strike each AI block out of the source, leaving the encyclical's words (as comments) in its place — breaking what it touches.# In one sense, technological innovation can represent human # participation in the divine act of creation. — Magnifica Humanitas §111 # (an AI invocation, relinquished)
Options
lindisfarner [OPTIONS] [FILE]
Page & layout:
-w, --width <N> Body text width [default: terminal width]
-b, --border <STYLE> none | simple | double | ornate [default: ornate]
-d, --drop-cap <WHICH> first | all | none [default: first]
--columns <N> set the text in N columns, codex-style [default: 1]
-j, --justify set the body flush to both margins
--hyphenate break over-long words with a trailing hyphen
--incipit rubricate the opening line
--fillers fill short closing lines with ❧ ornaments
-p, --pilcrows run paragraphs together, separated by an inline ¶
--drolleries adorn the left margin with ASCII marginal figures
--seed <N> vary which drolleries / scribal errors appear [0]
Colour & type:
-c, --color <WHEN> auto | always | never [default: auto]
-t, --theme <THEME> gold | crimson | mono [default: gold]
-f, --font <FONT> blackletter | standard [default: blackletter]
-r, --rubricate <W,..> words to highlight in the rubric colour
Code:
--code illuminate the input as source code
--prose force prose mode even for a recognised code file
--language <LANG> override the code-mode language (rust, python, c, …)
--corrupt introduce scribal errors that break the text (and code)
Search & magnifica (need Semgrep):
--find <PATTERN> find code by a Semgrep pattern and gloss the matches
--scan <RULES> scan with a Semgrep rule config (file or directory)
--magnifica <M> write the encyclical into AI-using code on disk
(witness | relinquish)
--quotes <FILE> passages file for the magnifica modes
Output & misc:
-o, --output <FILE> write to a file instead of stdout
--completions <SHELL> print a shell completion script and exit
--man print a roff man page and exit
Use as a library
lindisfarner is also a library crate. The CLI's dependencies (clap, Semgrep
glue, terminal sizing) sit behind the default cli feature, so a library user
can opt out of them:
Build a Config and call render:
use ;
let cfg = Config ;
let page = render;
print!;
The public surface is small:
render(source, &Config) -> String— illuminate prose, or source code withConfig { code: true, .. }.render_glossed(rows, &Config) -> String— render explicit(code, gloss)rows as a commentary page, each gloss set in the margin.detect_language(filename) -> Option<String>— the canonical language name for a path's extension, for auto-enabling code mode.Configand its enumsBorder,Theme,DropCap,Font, plus theMIN_WIDTHconstant.
The library carries no Semgrep dependency — the --find, --scan, and
--magnifica features live in the CLI binary only. Full API docs are on
docs.rs.
How it fits together
src/lib.rs— the public API:Configandrender.src/illuminate.rs— word-wrapping, justification, and the drop-cap composition (the core).src/border.rs— the frame and its flourishes.src/style.rs— the colour palette / themes.src/drollery.rs— the marginal menagerie.src/code.rs— code mode: keyword rubrication and comment glosses.src/scribe.rs— scribal corruption (--corrupt).src/cli.rs— the command-line surface (arguments →Config).src/search.rs— the Semgrep bridge (--find,--scan).src/magnifica.rs— the magnifica art-project modes (--magnifica).src/main.rs— the CLI: routing and input/output.
Shell completions & man page
Completions are available for bash, zsh, fish, PowerShell, and elvish.
Acknowledgements
This tool is a late wedding present for my dear friend Neil Douglas Reilly.