Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c3a81de
The reference tables are checked against the code
fabeat Oct 27, 2026
0a73edd
The claims the pages make are checked
fabeat Oct 27, 2026
6d59620
The build fails when an example does not build
fabeat Oct 27, 2026
98b9551
The forbidden phrases are looked for in the templates too
fabeat Oct 27, 2026
c39ba32
The Word page says what is checked and what is not
fabeat Oct 27, 2026
f8d819f
The reference tables are read of the code, and the pages carry what t…
fabeat Oct 27, 2026
de18b33
The claims the library now makes true are checked, not pending
fabeat Oct 27, 2026
8d56062
The shared page says what an inline style is
fabeat Oct 27, 2026
c395862
The slides half refuses a name its theme does not have
fabeat Oct 27, 2026
d181c03
The Word half writes what it cannot draw as its own text
fabeat Oct 27, 2026
e61e261
The pages say what the dialect has now
fabeat Oct 27, 2026
0e3b56d
The site does not say again what was corrected
fabeat Oct 27, 2026
49b1c67
The README says what the site checks
fabeat Oct 27, 2026
51847d0
The presentation index names what the round trip does not keep
fabeat Oct 27, 2026
ea4f28d
The slides half says what a template is
fabeat Oct 27, 2026
c9ca546
The Word half says what a document may hold
fabeat Oct 27, 2026
8a36ff9
The site does not say again what was corrected
fabeat Oct 27, 2026
a7288ad
The README says what the site checks
fabeat Oct 27, 2026
61358c0
The pages say what a theme scaffolded from a deck is, and what a diff…
fabeat Oct 27, 2026
9ade76a
The command line is checked in one place, and the claims are read in …
fabeat Oct 27, 2026
21d8c3b
The build checks the libraries out at the branch the repository names
fabeat Oct 27, 2026
52a67ad
A failure says what failed, in a class of its own
fabeat Oct 27, 2026
e436f33
A page is named once, and the deck a claim draws is named once
fabeat Oct 27, 2026
438fa5c
The Word pages say what the library does
fabeat Oct 28, 2026
3a16810
The suite asks the library what does not survive, and reads the whole…
fabeat Oct 28, 2026
e940b6f
The lists of attributes and of frontmatter keys are read out of the l…
fabeat Oct 28, 2026
ccadbea
The page quotes the sentences the library prints for a loss
fabeat Oct 28, 2026
220f887
The errors page names the two exceptions the library throws now
fabeat Oct 28, 2026
3af85bb
The exceptions that sit beside InvalidInput are checked against the code
fabeat Oct 28, 2026
fb0ba82
The page says which formats carry an object and which lose it
fabeat Oct 28, 2026
fea8f5e
A value the code holds is checked in the form the code writes it
fabeat Oct 28, 2026
d56b752
The README says which lists the check holds to
fabeat Oct 28, 2026
ccec13c
The page names the ceilings the library has
fabeat Oct 28, 2026
25ec500
The comments say what the code cannot
fabeat Oct 28, 2026
de12609
The walk reads a page without a counter it moves itself
fabeat Oct 28, 2026
5014186
The page says what a wordmark is
fabeat Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@ jobs:
# site is mounted under, so this one setting decides both the canonical addresses and
# the links in the built pages.
MARKDOWN_OFFICE_CANONICAL: https://markdown-office.github.io

# Which branch of each library and of the command line the site is built against.
#
# `main` is the library as it is released, and it is the right answer for almost every
# run. A pull request that documents a feature the library does not have on `main` yet
# cannot be built against `main` — the site renders its own examples with the library,
# so an example that uses a feature `main` does not have does not build — and saying
# which branch is wanted in the repository's settings is better than a build that fails
# on a feature the page is about.
#
# The variables are read at the point of use and default to `main` when they are not
# set, so nothing has to be done to them for an ordinary run, and setting them back to
# `main` — or removing them — puts the repository back as it was.
#
# Only a pull request reads them: the site published from `main` is always built
# against the libraries' own `main`, so the published site never depends on a branch
# of a library that may be deleted.
MARKDOWN_WORD_REF: ${{ github.event_name == 'pull_request' && vars.MARKDOWN_WORD_REF || 'main' }}
MARKDOWN_PRESENTATION_REF: ${{ github.event_name == 'pull_request' && vars.MARKDOWN_PRESENTATION_REF || 'main' }}
MARKDOWN_OFFICE_REF: ${{ github.event_name == 'pull_request' && vars.MARKDOWN_OFFICE_REF || 'main' }}
steps:
# This repository with the three libraries beside it, because the manifest's `path`
# entries resolve by directory and `../markdown-word` means nothing in any other
Expand All @@ -43,14 +63,14 @@ jobs:
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: markdown-office/markdown-word
ref: main
ref: ${{ env.MARKDOWN_WORD_REF }}
path: markdown-word

- name: Check out markdown-presentation
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: markdown-office/markdown-presentation
ref: main
ref: ${{ env.MARKDOWN_PRESENTATION_REF }}
path: markdown-presentation

- name: Check out commonmark-generic-directives
Expand All @@ -72,7 +92,7 @@ jobs:
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: markdown-office/mdoffice
ref: main
ref: ${{ env.MARKDOWN_OFFICE_REF }}
path: mdoffice

- name: Set up PHP
Expand Down Expand Up @@ -155,9 +175,27 @@ jobs:
"$RUNNER_TEMP/bin/wordcraft-cli" --version
"$RUNNER_TEMP/bin/deckcraft-cli" --version

# The strict build, not the plain one: an example that does not build is rendered
# into the page as "This example did not build", and the build succeeds anyway.
# A green build with a broken example on it is the failure this step exists to
# catch, so the strict flag is the one that is asked for.
- name: Build the site
working-directory: documentation
run: npm run build
run: npm run build:strict

# The reference tables are written by hand, and a hand-written list is correct
# only until the code moves. Each vocabulary is read out of the library that
# owns it and the page that carries it is read for the names it lists, and the
# two are compared in both directions.
- name: The reference tables list what the libraries have
working-directory: documentation
run: php tools/check-reference.php

# The claims the pages make in prose — the counts, the completeness, the rules —
# which a table cannot carry and a name cannot say.
- name: The claims the pages make
working-directory: documentation
run: vendor/bin/pest

# A site whose links 404 is worse than no site, and nothing else says so: the build
# succeeds, the pages are there, and the reader is the one who finds out.
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
/_data/examples.json
/patches.lock.json
/.worktrees/
/.phpunit.cache/
.DS_Store
65 changes: 63 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ npm run build # build the examples, then the pages, into _site
npm run serve # the same, with a watcher
```

```bash
composer check # the examples, the command-line pages, the reference tables, the claims
composer test # the claims on their own
```

```bash
npm run build:strict # ...and exit non-zero if an example does not build
```
Expand All @@ -41,6 +46,60 @@ fails the build rather than the reader's afternoon.
That is not decoration — it caught three real errors in these pages while they were being written,
including one where an example claimed a Word footnote that the converter did not produce.

## The reference tables

The tables are claims too, and they drift: the site said four themes shipped when five did, and
its error page said it listed every exception the package throws and left five of them out.

So the tables are checked against the code, and a table says which vocabulary it carries:

```markdown
<!-- reference: presentation.layout-roles -->

| Role | Title box |
| --- | --- |
| `title` | yes |
```

`tools/check-reference.php` reads every vocabulary out of the library that owns it — the frontmatter
keys, the layout roles, the free shapes, the colour slots, the theme keys, the style keys, the
exceptions, the themes that ship, the ceilings, the Word directives, the attributes each of the
Word directives reads, the keys a Word document's frontmatter may carry, the options the reader of
a Word document trusts, the features a Word format cannot carry, the Word options, the Word style
slots and the inline style attributes — and reads the page that carries it for the names it lists.
The two are compared in both directions: a name the code does not have is a page offering something
that does not work, and a name the code has and the page does not is a feature nobody can find.

A vocabulary the two halves share is read out of both of them and held to one list, so the
attributes an inline style takes are the same eight in the deck and in the document — and the
check says so when they are not.

Three rules make the lists stay lists:

- **One list, one home.** A second copy of a table is a failure, and so is a copy of part of one:
two copies drift apart and the one nobody reads is the one that is wrong. Every table that lists
names is marked, and the marked one is the only one.
- **The code is the only source.** The lists are read out of the libraries, not written down in the
site, so there is no second copy for the check to compare against. Where a library holds no list
of its own — the attributes a directive reads, the keys a frontmatter may carry — the code is
read for the names, and a name the code stops reading is a name the page may not list.
- **A vocabulary with no home is a failure.** A check that passes because it found nothing to check
is worse than no check at all.

A table may also carry a value the code holds, and then the value is checked too: the numbers in
the table of ceilings, and the exceptions that sit beside `InvalidInput` rather than under it.

What a table cannot hold is prose — what a key does, what an exception means — and a count written
out in a sentence ("Eight, and only these"). Those are claims too, and they are checked by the
suite in `tests/`: the counts against the code, the claims of completeness against the code, and the
rules against the library that has to behave that way. A sentence that was wrong and has been
corrected goes into the `FORBIDDEN` map of `tools/check-command-line.php`, and a page that says it
again is a failure.

The tables are not generated. Their other columns are prose that no code holds, and moving that
prose into a generator would make a second copy of the documentation in a file that is not a page —
the same drift, one repository further away.

## Writing a page

```text
Expand All @@ -49,7 +108,7 @@ content/
├── word/ the dialect, the directives, the configuration, the round trips, the images, the errors
├── presentation/ the dialect, the directives, the themes, the errors
├── general/ how directives work, the syntax, the images, the errors
├── command-line/ the installation, the basic use, the options of each half
├── command-line/ the installation, the basic use, the options of each part
├── suite/ the other packages
└── llms/ llms.txt.njk, pages.json.njk
```
Expand Down Expand Up @@ -178,7 +237,9 @@ to it, and the site is published at the GitHub Pages address until then.
content/ the pages, one directory per chapter
_includes/ the layout, the stylesheet, the wordmark
_documents/ the documents the examples read. Committed: an input must not change.
tools/ render-examples.php, make-input-document.php, the two link checks, and lib/
src/ the reference tables, read out of the libraries and compared with the pages
tests/ the claims the pages make in prose
tools/ render-examples.php, make-input-document.php, the three checks, and lib/
_examples/ built artifacts, published under the site's own path. Not committed.
_data/ examples.json, written by the example builder. Not committed.
_site/ the built site. Not committed.
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@
"examples": "@php tools/render-examples.php",
"build": "npm run build",
"serve": "npm run serve",
"test": "@php tools/check-command-line.php ../mdoffice",
"test": "pest",
"check": [
"@php tools/render-examples.php --strict",
"@php tools/check-command-line.php ../mdoffice",
"@php tools/check-readme-links.php ../markdown-word ../markdown-presentation ../mdoffice"
"@php tools/check-reference.php",
"@php tools/check-readme-links.php ../markdown-word ../markdown-presentation ../mdoffice",
"@test"
],
"check:site": "@php tools/check-site-links.php",
"check:links": "@php tools/check-readme-links.php ../markdown-word ../markdown-presentation ../mdoffice"
Expand Down
30 changes: 25 additions & 5 deletions content/command-line/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,26 @@ order: 1
[`markdown-office/mdoffice`](https://github.com/markdown-office/mdoffice), which requires both
converter packages and calls them — so what it does is what those libraries do.

The options of each half are on a page of their own: [For word](/command-line/word/) and [For
presentations](/command-line/presentations/). How to get the tool is on [Installation](/command-line/installation/).
The options of each part are on a page of their own: [For word](/command-line/word/), [For
presentations](/command-line/presentations/) and [For themes](/command-line/theme/). How to get the
tool is on [Installation](/command-line/installation/).

```bash
mdoffice word README.md # writes README.docx
mdoffice word README.docx # writes README.md
mdoffice slides deck.md # writes deck.pptx
mdoffice theme deck.pptx # writes deck/, a theme to edit
```

## Parts

The first argument says which converter the run is for.
The first argument says which part the run is for.

| Part | Does |
| --- | --- |
| `mdoffice word …` | Markdown to `.docx`, `.odt` and `.rtf`, and `.docx` back to Markdown |
| `mdoffice slides …` | Markdown to `.pptx`, and back |
| `mdoffice theme …` | A theme scaffolded from a deck |

**A bare file is not a run.** `mdoffice notes.md` is refused, and the parts are listed:

Expand All @@ -38,6 +41,7 @@ mdoffice: Unknown part "notes.md".

word Markdown to .docx, .odt and .rtf, and .docx back to Markdown
slides Markdown to .pptx, and back
theme A theme scaffolded from a deck
```

The Word half decides the direction from the file's first bytes, but with more than one converter
Expand All @@ -48,6 +52,9 @@ Only a `.docx` is read back. An `.odt` is a zip archive as well, so it is read a
document and refused — the reader knows the Word document format and not the OpenDocument one.
The two are written, not read.

The theme part is the one that converts nothing: it reads a deck and writes a theme directory,
which is a first draft of a theme and is meant to be edited.

## Commands

| Part | Command | Does |
Expand All @@ -58,15 +65,23 @@ The two are written, not read.
| `word` | `to-markdown` | a Word document to Markdown |
| `slides` | `to-pptx` | Markdown to `.pptx` |
| `slides` | `to-markdown` | a deck to Markdown |
| `slides` | `diff` | two decks, and what changed between them |
| `theme` | `init` | a theme scaffolded from a deck |

In the Word part, with no command, the direction is detected. `--to` overrides it: `docx` or
`word` for a Word document, `odt` for an OpenDocument Text, `rtf` for Rich Text Format, and
`markdown` or `md` for Markdown. Those are the spellings it takes; `opendocument` and `rich
text` are refused, because the command that runs says which format it reads.

The slides part has two commands, and the first is the default: `mdoffice slides deck.md` is
The slides part has three commands, and the first is the default: `mdoffice slides deck.md` is
the same run as `mdoffice slides to-pptx deck.md`. The second reads a deck back, and it is
named — a deck is a `.pptx`, and the part does not work the direction out for itself.
named — a deck is a `.pptx`, and the part does not work the direction out for itself. The third
compares two decks and says what changed between them; its options are on [For
presentations](/command-line/presentations/).

The theme part has one command, and it is the default: `mdoffice theme deck.pptx` is the same
run as `mdoffice theme init deck.pptx`. Its options are on [For
themes](/command-line/theme/).

A command named on the command line decides the direction itself, and `--to` is then only allowed
to agree with it:
Expand Down Expand Up @@ -95,6 +110,7 @@ mdoffice help
mdoffice word help
mdoffice word to-docx --help
mdoffice slides to-pptx --help
mdoffice theme help
```

## Exit codes
Expand All @@ -112,6 +128,10 @@ A defect is printed with its class and the line it happened on, because it is no
somebody's to see: `mdoffice word: <ExceptionClass>: <message>`, and then the file and the line
underneath it.

**A diff that finds differences has done what it was asked.** It exits `0`, because comparing two
decks and reporting the difference is the whole of the work; `--exit-code` is what turns the
difference into a failure, for a build to act on.

## Things it is easy to get wrong

- `--output=x`, `--output x` and `-o x` are the same. Leading dashes are stripped before the
Expand Down
65 changes: 62 additions & 3 deletions content/command-line/presentations.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ section: The command line
order: 2
---

The slides part has two commands. `mdoffice slides deck.md` is the same run as `mdoffice slides
to-pptx deck.md`, and the second reads a deck back. The options each takes are here; the
options the Word part takes are on [For word](/command-line/word/).
The slides part has three commands. `mdoffice slides deck.md` is the same run as `mdoffice slides
to-pptx deck.md`, the second reads a deck back, and the third compares two decks. The options
each takes are here; the options the Word part takes are on [For word](/command-line/word/), and
the theme part is on [For themes](/command-line/theme/).

## Options for `to-pptx`

Expand Down Expand Up @@ -39,6 +40,64 @@ A line ending may also be written `unix`, `n`, `dos`, `windows` or `r`.
The Markdown that comes back is the Markdown this tool writes decks in, so a deck can be read
out, edited as text and put in again through `to-pptx` with a different theme.

## Options for `diff`

| Option | Value | Default |
| --- | --- | --- |
| `--exit-code` | flag | off |
| `-h, --help` | flag | off |

A diff takes two decks and says what changed between them:

```bash
mdoffice slides diff before.pptx after.pptx
```

**The bytes are never compared.** A `.pptx` carries a timestamp in every part it holds and
another in the document's own properties, so two correct renderings of the same deck differ most
of the time; comparing the bytes would report every deck as changed.

**The slides are paired by their id**, because an id survives an edit and a reorder of the deck
and a position does not: a slide inserted at the front moves every slide after it, and pairing by
position would report the whole deck as changed. The shapes of a slide are paired by their id
first, then by their name, and then by their place. A slide or a shape that is left over was
added or removed, and the report says which.

**What is compared** is the text of every shape, the position and the size of every shape that
states them, and the colour of every fill and every run. A colour that names a slot of the theme
is compared as the colour that slot holds, so a change in the theme or in the master's colour
map is a change of colour. Positions and sizes are in millimetres, from the top left of the
slide, and two lengths are the same length when they agree to a hundredth of a millimetre.

**What is not compared** is said as well, because a diff that quietly skips what it could not
compare leaves a reader worse off than one that says what it looked at: the notes, the layouts,
the masters, the theme's own fonts, the pictures, the document's own properties, a fill that is
not a single colour, the frame of a group of shapes, and the position or the size of a shape
that takes it from the layout.

The report goes to standard error, so a pipe gets the difference and not the report of it. It
ends with what was looked at:

```text
Compared the text of every shape, the position and the size of every shape that states them, and the colour of every fill and every run, on the 3 slides the two decks have in common. A colour that names a slot of the theme is compared as the colour that slot holds, so a change in the theme or in the master's colour map is a change of colour.
Positions and sizes are in millimetres, from the top left of the slide. Slides are numbered as in the first deck.
Not compared: the notes, the layouts, the masters, the theme's own fonts, the pictures, the document's own properties, a fill that is not a single colour, the frame of a group of shapes, and the position or the size of a shape that takes it from the layout.
```

**A diff that finds differences has done what it was asked**, so it exits `0`. `--exit-code` is
what turns the difference into a failure, for a build to act on:

```bash
mdoffice slides diff before.pptx after.pptx --exit-code || echo "the decks differ"
```

A diff needs two decks, and one is refused:

```text
mdoffice slides: Name the two decks to compare.
mdoffice slides: A diff takes two: the deck before and the deck after.
```

**There is no `--to` in this part.** The command says which way the deck goes, and the deck
is a `.pptx` either way:

Expand Down
Loading
Loading