feat: robust docs
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user