Metadata-Version: 2.5
Name: panbuild
Version: 0.2.1
Summary: A literate build system for documents
Author-email: Olivier Schwander <olivier.schwander@chadok.info>
License-Expression: GPL-3.0-or-later
License-File: COPYING
Requires-Python: >=3.11
Requires-Dist: pandoc>=2.4
Requires-Dist: pyyaml>=6.0.1
Requires-Dist: setuptools>=75.5.0
Description-Content-Type: text/markdown

# Panbuild

A literate build system for documents.

Panbuild turns a YAML configuration file (`Panbuild.yml`) plus a Markdown
"index" file into a `build.ninja` file, which the [ninja] build tool then
executes to produce documents (HTML pages, PDF documents, slide decks, ...)
with [pandoc].

[ninja]: https://ninja-build.org
[pandoc]: https://pandoc.org

## How it works

The pipeline has two phases:

1. `panbuild` is run in a directory containing a `Panbuild.yml` file. It
   parses the index Markdown into a pandoc AST, rewrites the `!rule` links
   it contains into build statements, and emits three files in the current
   directory:

   - `build.ninja` — variables, rules, build statements and defaults;
   - `.index.native` — the index document in pandoc "native" format, with
     the `!rule` links rewritten to point at the built outputs in the
     build directory;
   - `.metadata.yml` — the `variables:` section of the configuration,
     dumped back as YAML so that it can be reused by pandoc defaults
     files (`-d` flag).

2. `ninja` executes `build.ninja`. The index itself is compiled to
   `<build directory>/index.html` from `.index.native`.

## Requirements

- Python 3.11 or later, managed with [uv]
- `ninja`
- the `pandoc` command-line tool (the Python `pandoc` package is only used
  to manipulate the document AST; the generated build commands shell out to
  the pandoc binary). Depending on your rules you may also need a PDF
  engine such as `lualatex`.

[uv]: https://docs.astral.sh/uv/

## Installation

```sh
uv sync
```

## Usage

```sh
cd example
uv run panbuild   # generate build.ninja
ninja             # build everything (ninja -n for a dry run)
```

`panbuild` reads `Panbuild.yml` from the current working directory; there
are no command-line options.

## The index file

The index is an ordinary Markdown file. Any link whose text starts with
`!` is a build target:

```markdown
[!document](toto.md)
[!document tata](tata.md)
[!copy blob](toto.raw)
```

`[!document tata](tata.md)` means "build `tata.md` with the `document`
rule". Panbuild emits the corresponding ninja statement and rewrites the
link, so that the generated index page points at the built output; the
`!rule` token is stripped from the link text (here the text becomes
"tata").

Targets are written to the build directory as
`<prefix>-<source stem>.<rule extension>` (for example
`build/m2dac_reds-tata.pdf`); rules without an `extension` keep the source
filename verbatim. Every generated target also gets a `default`
statement, so a bare `ninja` builds everything.

## Configuration

A typical `Panbuild.yml`:

```yaml
variables:
  title: Example of a Panbuild.yml file
  date: 2023
  author: Great panbuilder
build:
  index: index.md
  directory: build/
  prefix: m2dac_reds
  rules:
    html:
      extension: html
      command: pandoc $options -s --toc --toc-depth=1 -o $out $in
    document:
      extension: pdf
      command: pandoc $options -s -d .metadata.yml -o $out $in
    copy:
      command: cp $in $out
  header: |
    options = --pdf-engine=lualatex --variable=papersize:a4

    rule rsync
      command = rsync -aP --delete $directory server:public_html/
  footer: |
    build publish: rsync
```

### `variables`

Arbitrary metadata (title, author, date, theme, ...). Dumped verbatim to
`.metadata.yml`, typically consumed by pandoc defaults files.

### `build`

- `index`: path of the index Markdown file.
- `directory`: build directory for the generated outputs.
- `prefix`: prefix prepended to target names; a leading `$name` is
  expanded from the scalar `build` values.
- `rules`: mapping of rule names to ninja rules. `command` is the shell
  command (with the usual ninja `$in`/`$out` variables), `extension` is
  the output file extension.

  Note: an `html` rule is required, as the generated index page is always
  built with it.

- `header` / `footer`: raw ninja syntax injected verbatim near the top and
  at the end of `build.ninja`. Typically used for extra variables (such as
  `options`), extra rules, and phony aggregate targets like
  `build publish:`.

All scalar keys under `build:` are emitted verbatim as ninja variables —
including arbitrary keys that panbuild does not use itself — so they can be
referenced as `$directory`, `$unknownvar`, etc. inside commands. Two
caveats: values are formatted with Python `str()` (YAML `flag: true`
becomes `flag=True`), and keys under the top-level `variables:` section are
not passed to ninja at all; they end up in `.metadata.yml`.

## Example

The [`example/`](example/) directory contains a complete `Panbuild.yml`
and index file demonstrating the rules above. Note that the referenced
source files (`toto.md`, `tata.md`, `toto.raw`) are absent on purpose:
`panbuild` generation succeeds, only `ninja` complains about them.

## License

GPL-3.0-or-later, see [COPYING](COPYING).
