Skip to content

Docs as code

Practical 7.3 docs-as-code-annex.pdf · PDF

In May you handed over a working server. Today Marta, who built it, leaves Tramuntana Apps. On Monday at 8:00, Pau the intern gets a phone call: the design share is not reachable. What does he open?

If the answer is server-manual-final-v3.docx, sitting in somebody's Downloads folder, there is a second question nobody can answer: which version of the server does it describe? Documentation that nobody can trust is worse than none. This page shows how professionals keep it trustworthy — with the same tools you already use for code.

What you will learn

  • What docs as code means: plain text, versioned with Git, reviewed, built by one command
  • Convert a Markdown file into other formats with Pandoc
  • Follow the pipeline Markdown → Pandoc → Typst → PDF and say what each tool contributes
  • Write a small template and a build.sh that produces the PDF
  • Read a build error: which file, which line, what is wrong
  • Why "the PDF is the document" is a trap, and what a page another technician can follow looks like

The idea: documentation written like code

Docs as code means writing documentation with the same tools as code: plain text files, kept in version control, reviewed by another person, and turned into the final format by a program.

Ingredient What it gives you
Plain text (Markdown) Any editor opens it, on any system. Git can show what changed, line by line.
Version control (Git) Who changed what, when and why. Any old version can be recovered.
Review (pull request) A second person reads it before it becomes official.
A build (one command) The PDF is produced by a program, the same way every time. Nobody edits the PDF.

You already have the first three: you write your reports in Markdown, and the last two Git pages gave you a repository and pull requests. Today adds the fourth.

The pipeline

Back to the kitchen (we keep it all page)

In Unit 1 the computer was a kitchen. Here is a new dish. The Markdown files are the ingredients. The template is the plate: how the result looks. The script build.sh is the recipe: the exact steps, always the same. The PDF is the dish. If the dish is wrong, you do not repair the dish — you change an ingredient and cook again.

flowchart LR
    subgraph SRC ["In the repository (the ingredients)"]
        MD["<b>docs/*.md</b><br>the text"]
        TPL["<b>template.typ</b><br>the plate"]
    end
    subgraph BUILD ["In build/ (ignored by Git)"]
        BODY["<b>body.typ</b><br>text in Typst markup"]
        ALL["<b>tramuntana-docs.typ</b><br>template + text"]
        PDF["<b>tramuntana-docs.pdf</b><br>the dish"]
    end
    MD -->|"pandoc"| BODY
    TPL -->|"cat"| ALL
    BODY -->|"cat"| ALL
    ALL -->|"typst compile"| PDF

Two tools, two different jobs:

Pandoc is a converter between document formats: it translates notation (Markdown into HTML, Word, Typst...) without changing the content.

Typst is a typesetting program: it lays out text on pages — margins, fonts, numbered headings, contents — and produces a PDF.

Careful: Pandoc and Typst do not do the same thing

Pandoc translates between notations. Typst lays out pages. Our recipe uses each one for what it does best.

Three short stories

  • Markdown, March 2004: John Gruber, a writer, wanted text that reads well as plain text and still becomes a web page. Aaron Swartz helped shape the syntax. The first tool was one Perl script.
  • Pandoc, 2006: John MacFarlane, a philosophy professor at Berkeley, wrote a converter between document formats for his own use. It now reads and writes dozens of them; people call it the Swiss army knife of documents.
  • Typst: two students in Berlin, Laurenz Mädje and Martin Haug, wanted typesetting that is fast and readable. They opened the source code on 21 March 2023.

And since you can read licences now: Pandoc is GPL (copyleft), Typst is Apache 2.0 (permissive) — see Software by licence and purpose.

Installing the two tools

Pandoc comes from the Debian repositories. Typst is not packaged in Debian 13: it is one executable file, downloaded from the project's releases page and copied to ~/.local/bin, a folder you add to the PATH (the list of folders where the shell looks for commands, from Unit 5).

sudo apt install -y pandoc curl xz-utils
cd ~
curl -LO https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz
tar -xf typst-x86_64-unknown-linux-musl.tar.xz
mkdir -p ~/.local/bin
cp typst-x86_64-unknown-linux-musl/typst ~/.local/bin/
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
pandoc --version | head -1 ; typst --version
pandoc 3.1.11.1
typst 0.15.1 (9dfd3a08)

Your versions may be newer. If you get typst: command not found, the PATH line has not reached this terminal: run source ~/.bashrc or open a new terminal, and check with echo $PATH.

One source, three formats

Try Pandoc alone on your README:

cd ~/tramuntana-docs
pandoc README.md -o /tmp/readme.html
pandoc README.md -o /tmp/readme.docx
pandoc README.md -t typst

The first two write a web page and a Word file (Pandoc guesses the format from the extension). The third prints Typst markup on the screen:

$ pandoc README.md -t typst
= Tramuntana office server
<tramuntana-office-server>
Technical documentation of the Tramuntana Apps office server, Palma
\(Mallorca).

The Markdown heading # Title became the Typst heading = Title. The words did not change at all. Same content, another notation: that is all a converter does.

The template: the plate

The template holds the design of the PDF, and nothing else. Create template.typ, with your name, and leave an empty line at the end:

// Design of the PDF. The text comes from docs/*.md
#set document(title: "Tramuntana office server")
#set page(paper: "a4", margin: 2.5cm, numbering: "1 / 1")
#set text(lang: "en", size: 11pt)
#set heading(numbering: "1.1")
#show link: underline
#let horizontalrule = line(length: 100%)

#align(center + horizon)[
  #text(size: 24pt, weight: "bold")[Tramuntana office server]

  Technical documentation

  Your Name · 1st DAM · June 2027
]
#pagebreak()
#outline()
#pagebreak()
  • Lines starting with #set are rules for the whole document: paper and margins, text size, numbered headings.
  • The centred block is the title page.
  • #outline() writes the table of contents by itself, from the headings.

Where is the text of the documentation? Not here. This file is only the plate.

The build script: the recipe

Create build.sh:

#!/usr/bin/env bash
# Markdown -> Typst -> PDF
set -e
mkdir -p build docs/img
pandoc docs/*.md -t typst -o build/body.typ
cat template.typ build/body.typ > build/tramuntana-docs.typ
cp -r docs/img build/
typst compile build/tramuntana-docs.typ
echo "Built build/tramuntana-docs.pdf"
Line What it does
set -e Stop at the first command that fails, so a broken step does not produce a half-made PDF
mkdir -p build docs/img Make sure both folders exist
pandoc docs/*.md -t typst -o build/body.typ All the chapters, in the order of their file names, converted to Typst — that is why they are called 01-..., 02-...
cat template.typ build/body.typ > ... Glue: template first, text after
cp -r docs/img build/ The pictures travel with the text, so their paths still work
typst compile ... Typeset the glued file into a PDF next to it

Make it executable, build, and commit only the sources:

chmod +x build.sh
./build.sh
git status --short
git add template.typ build.sh
git commit -m "Add build script and template"

git status --short must not list build/: it is in your .gitignore since the Git basics page. If you get Permission denied on ./build.sh, the chmod +x is missing.

Careful: the PDF is not the document

The PDF is a product, like a compiled program. The document is the Markdown in the repository. Fix a typo directly in the PDF or in a .docx export, and the next build erases your fix. That is also why build/ is ignored by Git: anyone can cook the dish again.

Reading a build error

Break the template on purpose: change #outline() to #outlin() and run ./build.sh.

$ ./build.sh
error: unknown variable: outlin
   ┌─ build/tramuntana-docs.typ:17:1
   │
17 │ #outlin()
   │  ^^^^^^

Read every error in three parts:

Part Here
What is wrong unknown variable: outlin — Typst does not know that word
Where Line 17 of the glued file. The template comes first, so it is line 17 of template.typ
The exact place The ^ characters point at it

Repair it without even opening the editor: git restore template.typ, then ./build.sh again. The album pays off.

Message Usual cause
unclosed delimiter or unexpected ... A missing ] or ) in the template, near the line shown
pandoc: docs/*.md: withBinaryFile: does not exist You are not in ~/tramuntana-docs, or docs/ has no .md yet
An error in a line past the end of the template The problem is in a Markdown chapter — often a table whose rows do not all have the same number of pipe characters

Writing a page someone can follow

Good documentation is not long. It lets the reader act. The test for every page: could Pau do this at 8:00 on a Monday with nobody to ask? If a page only makes sense to the person who wrote it, it is a diary, not documentation.

There are two kinds of page — do not mix them in one paragraph:

Kind What it is Example
Reference Looked up, not read from the start The table of shares and permissions, the list of ports
How-to Followed step by step Add a new employee, restore a deleted file

A service page has five parts: Purpose · Installation · Configuration · Check · Sources. Here is what one looks like:

## File sharing (Samba)

**Purpose:** one shared folder per department, reachable from Windows and Linux.

```bash
sudo apt install samba
```

| Share  | Path              | Who can write  |
|--------|-------------------|----------------|
| dev    | /srv/shares/dev   | group `dev`    |

**Check:** `systemctl is-active smbd` answers `active`.

**Sources:** `man 5 smb.conf`, read 01/06/2027.

Three rules make the difference:

  • "Install Samba and configure it" is not documentation. Which command? Which file? Which lines? Configuration goes in a fenced code block, or it loses its line breaks.
  • Every command was run by you. Copy commands and output from your own terminal. An untested command in a manual is worse than no command.
  • Never a password. Write where the secret is kept, never the secret itself.

In Practical 7.3 (the button at the top) you build this whole pipeline and write docs/05-services.md for one service of your own ubu-srv, then commit and push it. That repository is the start of the assessed documentation of the unit.

Key takeaways

  • Docs as code: documentation in plain text, kept in Git, reviewed with pull requests, and built by one command.
  • Pandoc translates between formats (Markdown → HTML, Word, Typst) without changing the content; Typst lays out pages and produces the PDF.
  • The template holds the design, the Markdown holds the text, and build.sh is the recipe that joins them, the same way every time.
  • The PDF is a product: never edit it, change the source and build again; build/ stays in .gitignore.
  • A build error says what, where (file and line) and the exact place.
  • A good page lets a stranger act: reference or how-to, tested commands in code blocks, a Check, sources, and no passwords.

Quick check

1. In docs as code, which one is the real document?
  • The Markdown files in the repository
  • The PDF produced by the build
  • The Word file exported by Pandoc
  • The web page generated from the text

The source is the Markdown in Git. The PDF, the .docx and the web page are products built from it: if you edit them, the next build erases your change.

2. What is Pandoc's job in the pipeline of this page?
  • Translate the Markdown into Typst markup
  • Lay out the pages and produce the PDF
  • Upload the finished PDF to the forge
  • Check the spelling of every chapter

Pandoc is a converter: it changes notation, not content. The layout into pages is the job of Typst, and uploading is the job of git push.

3. Why is the build/ folder listed in .gitignore?
  • It can be generated again from the sources
  • It contains the passwords of the server
  • Git is not able to store PDF files
  • Typst refuses to write into a repository

Everything in build/ is produced by ./build.sh from the Markdown and the template. Like any build output, it is a product, so it stays out of the history.

4. The chapter files are called 01-overview.md, 02-software.md and so on. Why the numbers?
  • The pattern docs/*.md is read in name order
  • Git only accepts files that start with a number
  • Typst counts the files to number the pages
  • Pandoc needs a number to detect Markdown

The shell expands docs/*.md in alphabetical order of the names, so the numbers fix the order of the chapters in the PDF.

5. The build stops with error: unknown variable: outlin, at line 17 of build/tramuntana-docs.typ. Where is the mistake?
  • At line 17 of template.typ, a misspelt command
  • In the last Markdown chapter of docs/
  • In Pandoc, which must be reinstalled
  • In build.sh, which lacks permission to run

The glued file starts with the template, so its first lines are the template's lines. The error says what (an unknown word), where (line 17) and the ^ marks the exact place: #outlin() instead of #outline().

6. You spot a typo in the finished PDF of the server manual. What do you do?
  • Fix the Markdown source, commit and run the build again
  • Edit the PDF with a PDF editor and save it
  • Export a Word file with Pandoc and fix it there
  • Leave it, because the PDF cannot be changed

In docs as code you never repair the dish: you change the ingredient and cook again. A fix made in the PDF or a .docx disappears at the next build.

7. Pau will use your service page alone on a Monday morning. Select the two things the page must have.
  • The exact commands you ran, in a code block
  • A check command with the output Pau should expect
  • The administrator password, so Pau can log in
  • A long paragraph explaining Samba in general

A page is good when a stranger can act with it: tested commands in code blocks and a Check that proves the service works. Never write a password, only where it is kept; and a table plus three tested commands beat two pages of prose.

Next: the brief asks for an inventory with costs. A list of numbers that must add up is not a job for Markdown — it is a job for a spreadsheet. Office and collaborative tools.