128 lines
5.9 KiB
Markdown
128 lines
5.9 KiB
Markdown
# Typst export
|
|
|
|
The tool does not write Typst documents.
|
|
It loads a Typst file you own, finds the markers in it, and injects data.
|
|
Layout is yours; the payload is the tool's.
|
|
|
|
```console
|
|
$ coursebank template dump # get the built-in templates as files
|
|
$ coursebank template list # see which template each document uses
|
|
$ coursebank template config # write templates/typst.yaml
|
|
$ coursebank export typst exam-2 # render paper, key, and answer sheet
|
|
```
|
|
|
|
## Markers
|
|
|
|
Injection points are Typst line comments, so a template is a valid `.typ` file that compiles on its own:
|
|
|
|
```typst
|
|
// coursebank:begin questions
|
|
#render-question((number: 1, stem: [Sample.], options: ()))
|
|
// coursebank:end questions
|
|
```
|
|
|
|
Everything *between* the markers is replaced.
|
|
The marker lines survive.
|
|
Two consequences:
|
|
|
|
- The bundled templates ship with sample data inside their regions, so `typst watch templates/exam.typ` works before you have exported anything.
|
|
Restyle against the sample, then export.
|
|
- **An exported document is itself a valid template.** Exporting into a file you have since restyled replaces the questions and leaves your edits alone.
|
|
This is the difference between a generator you can use twice and one you copy out of once.
|
|
|
|
A bare `// coursebank:questions` also works.
|
|
It is rewritten into a region on output, so the second export behaves like every one after it.
|
|
|
|
Malformed markers are reported all at once, with line numbers, and the export stops: an unknown slot name, an unclosed region, a stray `end`, a nested region, or the same slot claimed twice.
|
|
|
|
## Slots
|
|
|
|
| Slot | Injected |
|
|
|:--|:--|
|
|
| `meta` | `#let cb-meta = (...)`: course, assessment, form, totals, objectives |
|
|
| `questions` | one `#render-question((...))` call per printed item |
|
|
| `data` | `#let cb-data = (...)`: the metadata and the questions together |
|
|
|
|
`questions` unrolls the loop using the record's own numbering.
|
|
`data` hands you the array and gets out of the way; the bundled key and answer sheet use it because a table suits a loop better than a sequence of calls.
|
|
Use either, both, or neither.
|
|
Only slots your template actually contains are built, so nothing costs anything until it is asked for.
|
|
|
|
Each question arrives as a **single positional dictionary**, not named arguments, so turning a field on or off in the config never changes your function's signature.
|
|
Read optional fields with `q.at("level", default: none)`.
|
|
|
|
## Configuration
|
|
|
|
`templates/typst.yaml`, three layers, each overriding the last: built-in defaults for the variant, then `defaults:`, then `variants:`.
|
|
|
|
```yaml
|
|
defaults:
|
|
question_fn: render-question # the function the `questions` slot calls
|
|
content: content # `content` -> [...] | `str` -> "..." for eval()
|
|
letters: upper # upper | lower | numeric | roman | nothing
|
|
extra:
|
|
accent: "#017ab9"
|
|
font: Roboto
|
|
|
|
variants:
|
|
key:
|
|
reveal: everything
|
|
extra:
|
|
show-solutions: true
|
|
```
|
|
|
|
Keys are snake_case, matching every other coursebank YAML file.
|
|
Keys *under* `extra` are yours and reach Typst verbatim, so they conventionally use hyphens.
|
|
|
|
`coursebank template config --resolved exam` prints what a variant actually ends up with, which is the quickest way to find out which layer won.
|
|
|
|
### `extra` is the escape hatch
|
|
|
|
Anything under `extra` is carried through untouched and arrives as `extra` in the payload.
|
|
Tier colours, a font stack, a `show-solutions` flag, a watermark, column counts.
|
|
Put it there and read it in the template.
|
|
Nothing about appearance needs to be added to this crate.
|
|
|
|
### `reveal` is not cosmetic
|
|
|
|
`reveal` controls whether the payload contains the answer **at all**.
|
|
The exam variant uses `nothing`, and that means an option dictionary on the paper has no `correct` field, not `correct: false`.
|
|
A template cannot leak a field it was never given, and that stays true after someone edits the template without reading this page.
|
|
|
|
Do not set `reveal: key` on the exam variant to build a solutions copy.
|
|
Export the `key` variant.
|
|
The failure mode of the other approach is one forgotten `if` away, and it is discovered by the whole room at once.
|
|
|
|
## Template lookup
|
|
|
|
1. `--template <path>`, or `template:` in the render config
|
|
2. `templates/<assessment-id>-<variant>.typ`, a one-off layout for one exam
|
|
3. `templates/<variant>.typ`, the course's own default
|
|
4. the template compiled into the binary
|
|
|
|
`coursebank template list` prints this chain with the resolved entry marked, plus the slots each template declares.
|
|
`coursebank template dump` writes step 4 into step 3.
|
|
|
|
## JSON
|
|
|
|
`coursebank export typst exam-2 --json` also writes the payload as JSON, for a template that reads `json("exam-2-A.json")` instead of taking an injected region.
|
|
Key spellings are identical between the two paths (`level-name`, not `level_name`), so a template can move between them without edits.
|
|
JSON has no content type, so set `content: str` and `eval(q.stem, mode: "markup")` if you use this path.
|
|
|
|
## What is still guaranteed
|
|
|
|
**The key matches its paper.** Option order comes from the form's recorded seed via `select::option_order`, never from anything stored, and the paper, key, and answer sheet are built from one payload.
|
|
Every export of form B agrees with every other.
|
|
|
|
**Question numbers are the recorded ones.**
|
|
Not positions on the page.
|
|
The recorded number is the join key to every grading export and response row; renumbering after a drop breaks that join silently, and the symptom is item statistics attributed to the wrong question.
|
|
`number-from-record: false` exists but you almost certainly want it left alone.
|
|
|
|
## Advisory warnings
|
|
|
|
`export typst` exits `2` and prints warnings, without refusing to write, when:
|
|
|
|
- a stem or option has unbalanced `[` `]`, which would otherwise surface as a Typst parse error somewhere downstream of the item that caused it, with no way for the compiler to name the question
|
|
- a template declares no markers at all, so nothing was injected
|