Generate PDF databooks from AsciiDoc or Markdown chapters with Jinja2 templating
Project description
databook-generator
Generate polished PDF databooks from AsciiDoc or Markdown chapter files, with full Jinja2 templating throughout — including inside SVG diagrams.
Define your parameters once in a config.json file. Every parameter automatically becomes a variable you can reference inside your chapter content and your SVG images.
Features
- AsciiDoc (
.adoc) chapters rendered viaasciidoctor-pdf - Markdown (
.md) chapters converted to AsciiDoc viapandoc, then rendered to PDF with the same pipeline - Jinja2 templating in
.adoc,.md, and.svgfiles — any key inconfig.jsonbecomes a template variable - Logo support — drop
logo.svgorlogo.pngin yourimages/folder; it appears on the title page and in the top-right corner of every page - Per-page footer with document title and page numbers
- Auto-discovery of
asciidoctor-pdfbinary across Homebrew, gem user-install, rbenv, and RVM - Clean intermediate file handling — no leftover artefacts unless you ask for them
Requirements
Python
Python 3.8 or newer.
asciidoctor-pdf (required for all PDFs)
asciidoctor-pdf is a Ruby gem and must be installed separately.
macOS
# If you have the system Ruby (not recommended — no write permission):
gem install asciidoctor-pdf --user-install
# Recommended: install Ruby via Homebrew first
brew install ruby
$(brew --prefix ruby)/bin/gem install asciidoctor-pdf
# Add Homebrew gem bin to your PATH permanently (add to ~/.zshrc or ~/.bashrc):
export PATH="$(brew --prefix ruby)/bin:$PATH"
export PATH="$($(brew --prefix ruby)/bin/gem environment gemdir)/bin:$PATH"
Linux (Debian / Ubuntu)
sudo apt update
sudo apt install ruby-full build-essential
gem install asciidoctor-pdf
Linux (Fedora / RHEL / CentOS)
sudo dnf install ruby ruby-devel
gem install asciidoctor-pdf
Windows
- Download and install Ruby from rubyinstaller.org (include the MSYS2 toolchain when prompted).
- Open a new Command Prompt or PowerShell:
gem install asciidoctor-pdf
Verify installation
asciidoctor-pdf --version
# If not found, locate the gem bin directory:
gem environment gemdir
# Then add <gemdir>/bin to your PATH
pandoc (required only for Markdown chapters)
pandoc is only needed when your chapters are .md files.
macOS
brew install pandoc
Linux (Debian / Ubuntu)
sudo apt install pandoc
Linux (Fedora / RHEL)
sudo dnf install pandoc
Windows
winget install --id JohnMacFarlane.Pandoc
# Or download the installer from https://pandoc.org/installing.html
Verify installation
pandoc --version
Installation
From PyPI
pip install databook-generator
With Markdown support
pip install "databook-generator[markdown]"
From source
git clone https://github.com/rohaansch/databook-generator.git
cd databook-generator
pip install -e . # AsciiDoc chapters only
pip install -e ".[markdown]" # Include Markdown support
Quick Start
1. Create your working directory
my-databook/
├── config.json
├── chapters/
│ ├── introduction.adoc (or introduction.md)
│ └── chapter2.adoc (or chapter2.md)
└── images/
├── logo.svg (optional — auto-detected)
└── my_diagram.svg
2. Write your config
{
"title": "My Databook",
"version": "1.0",
"author": "Your Name",
"date": "April 2026",
"product_name": "Widget Pro",
"chapters": [
"introduction",
"chapter2"
]
}
3. Run
cd my-databook/
databook-generator -config config.json
The PDF is written to my-databook/My_Databook.pdf.
CLI Reference
databook-generator -config CONFIG [options]
Required:
-config FILE Path to the JSON config file
Optional:
-images DIR Images directory (default: ./images)
-chapters DIR Chapters directory (default: ./chapters)
-output FILE Output PDF path (default: <title>.pdf in CWD)
--keep-intermediates Keep rendered .adoc and theme files for debugging
--version Show version and exit
-h, --help Show help
Config File Reference
config.json drives both the document structure and Jinja2 template variables.
| Key | Required | Description |
|---|---|---|
title |
Yes | Document title — used on the title page, footer, and output filename |
chapters |
Yes | Ordered list of chapter names (without extension) |
author |
No | Author name displayed on the title page |
| Any other key | No | Automatically available as a Jinja2 variable in chapters and SVGs |
Example
{
"title": "Specification Document",
"version": "2.1",
"author": "Rohan Chadhury",
"date": "April 2024",
"support_email": "support@example.com",
"feature_flags": {
"include_legacy_notes": false,
"include_new_section": true
},
"chapters": [
"introduction",
"measurements",
"api_example"
]
}
Jinja2 Templating
This is the core power feature of databook-generator. Every key you define in config.json is injected as a Jinja2 variable into your chapter files and SVG images at render time.
How it works
When you run databook-generator, it:
- Loads your
config.json - Passes every key (except
"chapters") as a Jinja2 variable - Renders each chapter file as a Jinja2 template
- Renders any SVG files that contain Jinja2 syntax
- Assembles and generates the final PDF
Variable substitution
Use {{ variable_name }} anywhere in your chapter or SVG:
# In config.json:
"author": "Rohan Chadhury",
"date": "April 2026",
"revision": "Rev1.0"
# In a chapter file (.adoc or .md):
This document covers the Measurements for revision {{ revision }} released on date {{ date }}.
Renders to:
This document covers the Measurements for revision Rev1.0 released on date April 2026
Conditional content
Use {% if %} / {% else %} / {% endif %} to include or exclude sections:
# config.json:
"feature_flags": { "include_legacy_notes": false }
# chapter.adoc:
{% if feature_flags.include_legacy_notes %}
NOTE: This section contains legacy notes for pre-{{ revision }} users.
...
{% endif %}
If include_legacy_notes is false, that entire block is omitted from the PDF.
Loops
Use {% for %} to generate repeated content from a list:
# config.json:
"layer_stack": ["M1", "M2", "M3", "M4", "M5"]
# chapter.adoc:
The following measurements are defined in {{ revision }}:
{% for timing in measurements %}
* {{ timing }}
{% endfor %}
Nested values
Access nested config keys using dot notation:
# config.json:
"timing": { "setup_margin": "50ps", "hold_margin": "20ps" }
# chapter.md:
Setup margin: **{{ timing.setup_margin }}**
Hold margin: **{{ timing.hold_margin }}**
SVG templating
SVG files in your images/ directory are also Jinja2 templates.
Any SVG containing {{ or {% is rendered before embedding in the PDF.
<!-- images/product_image.svg -->
<text x="300" y="50">{{ document_name }} — {{ serial_number }}</text>
<text x="300" y="70">Rev: {{ revision }}</text>
This is useful for diagram titles, labels, and revision stamps that should always match the config — no need to manually update SVG text when values change.
Undefined variables
If a template references a variable not present in config.json, it silently
renders as an empty string (no errors). This lets you write reusable chapter
templates that gracefully degrade for configs that don't define every variable.
Chapter Formats
AsciiDoc (.adoc)
AsciiDoc is a rich markup format with native support for tables, cross-references, callouts, and admonitions. It is the recommended format for complex technical documents.
== Introduction
This spec covers *{{ document_name }}* revision *{{ revision }}*.
[cols="1,2", options="header"]
|===
| Field | Value
| Document Title | {{ document_name }}
| Revision | {{ revision }}
|===
[NOTE]
====
All measurements are in nanometers.
====
<<<
The <<< at the end of a chapter inserts a page break.
Markdown (.md)
Markdown chapters use standard GitHub-Flavoured Markdown (GFM).
Under the hood they are converted to AsciiDoc via pandoc before PDF generation,
so the same logo, theme, and footer apply.
## Introduction
This report covers **{{ product_name }}** on the **{{ model_number }}** node.
| Field | Value |
|---|---|
| Product | {{ product_name }} |
| Node | {{ model_number }} |
{% if include_appendix %}
> See the appendix for raw measurement data.
{% endif %}
Format rules
- All chapters in a run must use the same format — either all
.adocor all.md. - Do not mix
.adocand.mdin the samechapterslist. The tool will exit with an error if mixed files are detected. - The extension is not included in
config.json; the tool discovers it automatically.
Logo
Place one or more logo files in your images/ directory. No configuration is needed — detection is automatic. SVG is recommended for crisp rendering at all sizes.
Two-logo mode (recommended)
Use separate files optimised for each context:
| File | Where it appears | Recommended size |
|---|---|---|
main_logo.svg / main_logo.png |
Centred on the title page at 45% page width | Wide, high-resolution |
header_logo.svg / header_logo.png |
Top-right of every page header at 22% page width | Horizontal / compact |
Single-logo mode (fallback)
If dedicated files are not found, logo.svg / logo.png is used for both roles automatically.
Priority order
Title page → main_logo.svg/png → logo.svg/png
Header → header_logo.svg/png → logo.svg/png
SVG logo templating
Logo SVG files can contain Jinja2 directives just like chapter files. For example, to stamp a version number on the logo:
<!-- images/header_logo.svg -->
<text x="200" y="60">Rev {{ version }}</text>
Directory Layout
The expected layout when running from a project directory:
my-databook/
├── config.json Required
├── chapters/ Default chapter directory (override with -chapters)
│ ├── intro.adoc or intro.md
│ └── section2.adoc or section2.md
└── images/ Default images directory (override with -images)
├── logo.svg Auto-detected logo
└── diagram.svg Referenced from chapters as image::diagram.svg[]
Override defaults:
databook-generator \
-config /path/to/config.json \
-chapters /path/to/my/chapters \
-images /path/to/my/images \
-output /path/to/output/report.pdf
Syntax Highlighting
Code blocks in chapters use Rouge for syntax highlighting. Install it for coloured output:
gem install rouge
Python API
DatabookBuilder lets you drive the entire build from Python code — no CLI, no config.json required.
Basic usage
from databook_generator import DatabookBuilder
builder = DatabookBuilder(
config={
"title": "Device Characterization Report",
"version": "3.0",
"product": "XR-9000",
"process_node": "5nm FinFET",
"chapters": ["overview", "measurements"],
},
chapters_dir="chapters",
images_dir="images",
)
builder.build(output="report.pdf")
You can also pass a path to a JSON file instead of a dict:
builder = DatabookBuilder(config="config.json", chapters_dir="chapters", images_dir="images")
Injecting custom chapters
Use add_chapter() to insert a dynamically generated chapter at any position.
The chapter content is a Jinja2 template; you supply the variables at call time.
builder.add_chapter(
template_path="templates", # directory containing the template
template_file="cell_library.adoc", # .adoc or .md template
variables={ # Jinja2 variables for this chapter only
"cells": [
{
"name": "NAND2_X1",
"description": "2-input NAND gate",
"inputs": ["A", "B"],
"output": "ZN",
"area_um2": 0.42,
},
{
"name": "INV_X1",
"description": "Inverter",
"inputs": ["A"],
"output": "ZN",
"area_um2": 0.21,
},
]
},
images=[ # images this chapter needs
"templates/images/nand2_schematic.svg",
"templates/images/inv_schematic.svg",
],
after="overview", # insert after the 'overview' chapter
name="cell_library", # logical name for after/before lookups
)
print(builder.chapter_names)
# → ['overview', 'cell_library', 'measurements']
builder.build()
Positioning options
| Parameter | Behaviour |
|---|---|
after="chapter_name" |
Insert immediately after the named chapter |
before="chapter_name" |
Insert immediately before the named chapter |
position=N |
Insert at zero-based index N |
| (none) | Append to the end |
add_chapter() returns self, so calls can be chained.
Chapter-local variables
Variables passed to add_chapter(variables=...) are merged over the global config variables — chapter-level keys override global ones for that chapter's scope only. Global variables are still available in the template.
Keeping intermediate files
Pass keep_intermediates=True to build() to retain the rendered .adoc files and theme YAML for debugging:
builder.build(output="report.pdf", keep_intermediates=True)
Examples
The example/ directory contains three complete working examples:
AsciiDoc example
cd example/adoc_example/
databook-generator -config config.json
Demonstrates:
- Jinja2 variable substitution and conditionals in
.adoc - SVG diagrams with Jinja2 labels rendered at build time
- Logo on every page
Markdown example
cd example/markdown_example/
databook-generator -config config.json
Demonstrates:
- Jinja2 variable substitution and conditionals in
.md - GFM tables and code fences
- Chart SVGs referenced from Markdown
Python API example
cd example/api_example/
python generate_report.py
Demonstrates the full Python API:
DatabookBuilderinstantiated with a config dict loaded from JSON- A custom
Cell Librarychapter generated from a Jinja2 template and a list of cell dicts - Cell schematics injected as chapter-local images
- Chapter inserted between
overviewandmeasurementsusingafter="overview"
The example directory layout:
example/api_example/
├── config.json Standard config (overview + measurements)
├── chapters/ Standard chapter files
│ ├── overview.adoc
│ └── measurements.adoc
├── images/ Shared images (logo, charts)
├── templates/ Custom chapter templates
│ ├── cell_library.adoc Jinja2 template — loops over a list of cells
│ └── images/ Cell-specific schematics
│ ├── nand2_schematic.svg
│ └── inv_schematic.svg
└── generate_report.py Python script using DatabookBuilder
Releasing to PyPI
Setup (one time)
- Create a PyPI account
- Enable Trusted Publishing in your PyPI project settings, pointing to this repository and the
publish.ymlworkflow
Release process
# Bump version in:
# databook_generator/__init__.py (VERSION)
# pyproject.toml (version field)
# CHANGELOG.md (add entry)
git add .
git commit -m "Release v1.1.0"
git tag v1.1.0
git push origin main --tags
The GitHub Actions workflow (.github/workflows/publish.yml) automatically builds
and publishes to PyPI when a v*.*.* tag is pushed.
Contributing
Pull requests are welcome. For major changes, open an issue first.
- Fork the repo
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes and add tests
- Push and open a PR against
main
License
MIT — © 2024 Rohan Chadhury
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file databook_generator-1.0.3.tar.gz.
File metadata
- Download URL: databook_generator-1.0.3.tar.gz
- Upload date:
- Size: 182.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6852fa77be3bb705c3b322d087f8278fb3b0c76fdf502dc6ca9db95e05f3d7b2
|
|
| MD5 |
342b74129612f468a53f437643878d41
|
|
| BLAKE2b-256 |
383f09b50219139e39b62ff6e358d1b0ef9a0a82d0e4f1b211125cf3edaf7a06
|
File details
Details for the file databook_generator-1.0.3-py3-none-any.whl.
File metadata
- Download URL: databook_generator-1.0.3-py3-none-any.whl
- Upload date:
- Size: 19.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b0552f5c232a5d42de0647e39f7370dec869817cf6950acfc779d77102c2926
|
|
| MD5 |
494ac7e7efe1943d382f21422a22f5cc
|
|
| BLAKE2b-256 |
51bd3aa549c69b9ccdac6adcd5328b7f12168296fb61b57ad71c68e2059a5d9e
|