diff --git a/.codex/skills/openspec-archive-change/SKILL.md b/.codex/skills/openspec-archive-change/SKILL.md index 9b1f851..51e529c 100644 --- a/.codex/skills/openspec-archive-change/SKILL.md +++ b/.codex/skills/openspec-archive-change/SKILL.md @@ -82,7 +82,17 @@ Archive a completed change in the experimental workflow. mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- ``` -6. **Display summary** +6. **Finalize synced spec purpose** + + If specs were synced or created under `openspec/specs/`: + - Open each affected main spec at `openspec/specs//spec.md` + - Check the `## Purpose` section + - If it contains the autogenerated archive placeholder (for example, + `TBD - created by archiving change ...`) or is still too generic, replace + it with a concise, capability-specific purpose statement before reporting + completion + +7. **Display summary** Show archive completion summary including: - Change name @@ -112,3 +122,4 @@ All artifacts complete. All tasks complete. - Show clear summary of what happened - If sync is requested, use openspec-sync-specs approach (agent-driven) - If delta specs exist, always run the sync assessment and show the combined summary before prompting +- If archiving creates or updates canonical specs, do not leave autogenerated placeholder `## Purpose` text behind diff --git a/.codex/skills/typst-grammar-authoring/SKILL.md b/.codex/skills/typst-grammar-authoring/SKILL.md new file mode 100644 index 0000000..bd7752e --- /dev/null +++ b/.codex/skills/typst-grammar-authoring/SKILL.md @@ -0,0 +1,508 @@ +--- +name: typst-grammar-authoring +description: Use when authoring or validating Typst documents from canonical grammar examples, especially when you need compile, HTML, or SVG-based validation workflows. +metadata: + short-description: Author Typst docs from canonical grammar examples + from-tutorial-rev: https://github.com/typst-doc-cn/tutorial/tree/971815a6b4898c0339b620e08c62824ec03bba7f +--- + +# Typst Grammar Authoring + +Use this skill when the user wants help drafting, fixing, or validating Typst +documents. Everything needed for grammar lookup lives in this file so it can be +copied into another repository without sibling scripts or reference files. + +## Workflow + +1. Start with the grammar lookup section in this file and pick the closest + existing pattern before inventing new syntax. +2. Copy the smallest matching example, then adapt it incrementally. +3. Run `typst compile` after each meaningful edit. Any non-zero exit code is a + blocking failure. +4. After compile succeeds, use HTML output to inspect rendered text and + document structure when wording or content ordering matters. +5. Use SVG output plus Playwright MCP when you need to inspect visual layout, + spacing, numbering, line breaks, or emphasis. + +## Validation + +Compile validation: + +```sh +typst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document.pdf +``` + +Text validation through HTML: + +```sh +typst compile --root . --features html path/to/document.typ target/typst-grammar-authoring-check/document.html +rg "expected text" target/typst-grammar-authoring-check/document.html +``` + +Visual validation through SVG: + +```sh +typst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document.svg +typst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document-{0p}.svg +``` + +Playwright inspection: + +- Use Playwright only after SVG generation succeeds. +- Open the SVG directly if the MCP server supports local files. +- Otherwise use a tiny local HTML wrapper that embeds the SVG, then capture a + screenshot and inspect layout, spacing, numbering, line breaks, and emphasis. + +## Guardrails + +- Keep all skill-authored prose and instructions in English. +- Canonical syntax examples may retain non-English literals from their source + examples. +- Do not treat Tinymist or editor diagnostics as the source of truth. +- Do not assume sibling updater scripts, reference files, or repo-local + metadata exist when using this skill elsewhere. +- Use `{p}` or `{0p}` in multi-page SVG output paths. +- Treat HTML export as a validation aid, not a production contract. +- Keep command examples platform-neutral by using forward-slash or placeholder + paths. + +## Grammar Lookup + +This section is embedded on purpose so the skill stays self-contained. Examples +are derived from the unofficial tutorial grammar samples and kept compact so the +lookup remains usable in a single file. + + + +### Base Elements + +- `paragraph`: `writing-markup` +- `heading`: `= Heading`; `== Heading` +- `strong`: `*Strong*` +- `emph`: `_emphasis_`; `*_emphasis_*` +- `list`: + +```typ ++ List 1 ++ List 2 +``` + +- `continue-list`: + +```typ +4. List 1 ++ List 2 +``` + +- `emum`: + +```typ +- Enum 1 +- Enum 2 +``` + +- `mix-list-emum`: + +```typ +- Enum 1 + + Item 1 +- Enum 2 +``` + +- `raw`: + +```typ +`code` +``` + +- `long-raw`: + +````typ +``` code``` +```` + +- `lang-raw`: + +````typ +```rs trait World``` +```` + +- `blocky-raw`: + +````typ +```typ += Heading +``` +```` + +- `image`: `#image("/assets/files/香風とうふ店.jpg", width: 50pt)` +- `image-stretch`: `#image("/assets/files/香風とうふ店.jpg", width: 50pt, height: 50pt, fit: "stretch")` +- `image-inline`: `在一段话中插入一个#box(baseline: 0.15em, image("/assets/files/info-icon.svg", width: 1em))图片。` +- `figure`: + +````typ +#figure(```typ +#image("/assets/files/香風とうふ店.jpg") +```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码]) +```` + +- `link`: `#link("https://zh.wikipedia.org")[维基百科]` +- `http-link`: `https://zh.wikipedia.org` +- `internal-link`: + +```typ +== 某个标题 +#link()[链接到某个标题] +``` + +- `table`: `#table(columns: 2, [111], [2], [3])` +- `table-align`: `#table(columns: 2, align: center, [111], [2], [3])` +- `inline-math`: `$sum_x$` +- `display-math`: `$ sum_x $` +- `escape-sequences`: `>\_<` +- `unicode-escape-sequences`: `\u{9999}` +- `newline-by-space`: `A \ B` +- `newline`: + +```typ +A \ +B +``` + +- `shorthand`: `北京--上海` +- `shorthand-space`: `A~B` +- `inline-comment`: `// 行内注释` +- `cross-line-comment`: + +```typ +/* 行间注释 + */ +``` + +- `box`: `在一段话中插入一个#box(baseline: 0.15em, image("/assets/files/info-icon.svg", width: 1em))图片。` + +### Text Styling + +- `highlight`: `#highlight[高亮一段内容]` +- `underline`: `#underline[Language]` +- `underline-evade`: + +```typ +#underline( + evade: false)[ጿኈቼዽ] +``` + +- `overline`: `#overline[ጿኈቼዽ]` +- `strike`: `#strike[ጿኈቼዽ]` +- `subscript`: `威严满满#sub[抱头蹲防]` +- `superscript`: `香風とうふ店#super[TM]` +- `text-size`: `#text(size: 24pt)[一斤鸭梨]` +- `text-fill`: `#text(fill: blue)[蓝色鸭梨]` +- `text-font`: `#text(font: "Microsoft YaHei")[板正鸭梨]` + +### Script Declarations + +- `enter-script`: `#1` +- `code-block`: `#{"a"; "b"}` +- `content-block`: `#[内容块]` +- `none-literal`: `#none` +- `false-literal`: `#false` +- `true-literal`: `#true` +- `integer-literal`: `#(-1), #(0), #(1)` +- `n-adecimal-literal`: `#(-0xdeadbeef), #(-0o644), #(-0b1001)` +- `float-literal`: `#(0.001), #(.1), #(2.)` +- `exp-repr-float`: `#(1e2), #(1.926e3), #(-1e-3)` +- `string-literal`: `#"Hello world!!"` +- `str-escape-sequences`: `#"\""` +- `str-unicode-escape-sequences`: `#"\u{9999}"` +- `array-literal`: `#(1, "OvO", [一段内容])` +- `dict-literal`: `#(neko-mimi: 2, "utterance": "喵喵喵")` +- `empty-array`: `#()` +- `empty-dict`: `#(:)` +- `paren-empty-array`: `#(())` +- `single-member-array`: `#(1,)` +- `var-decl`: `#let x = 1` +- `func-decl`: `#let f(x) = x * 2` +- `closure`: `#let f = (x, y) => x + y` +- `named-param`: `#let g(named: none) = named` +- `variadic-param`: `#let g(..args) = args.pos().join([、])` +- `destruct-array`: `#let (one, hello-world) = (1, "Hello, World")` +- `destruct-array-eliminate`: `#let (_, second, ..) = (1, "Hello, World", []); #second` +- `destruct-dict`: `#let (neko-mimi: mimi) = (neko-mimi: 2); #mimi` +- `array-remapping`: + +```typ +#let (a, b, c) = (1, 2, 3) +#let (b, c, a) = (a, b, c) +#a, #b, #c +``` + +- `array-swap`: + +```typ +#let (a, b) = (1, 2) +#((a, b) = (b, a)) +#a, #b +``` + +- `placeholder`: + +```typ +#let last-two(t) = { + let _ = t.pop() + t.pop() +} +#last-two((1, 2, 3, 4)) +``` + +### Script Statements + +- `if`: + +```typ +#if true { 1 }, +#if false { 1 } else { 0 } +``` + +- `if-if`: + +```typ +#if false { 0 } else if true { 1 }, +#if false { 2 } else if false { 1 } else { 0 } +``` + +- `while`: + +```typ +#{ + let i = 0; + while i < 10 { + (i * 2, ) + i += 1; + } +} +``` + +- `for`: + +```typ +#for i in range(10) { + (i * 2, ) +} +``` + +- `for-destruct`: `#for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\ ]` +- `break`: `#for i in range(10) { (i, ); (i + 1926, ); break }` +- `continue`: + +```typ +#for i in range(10) { + if calc.even(i) { continue } + (i, ) +} +``` + +- `return`: + +```typ +#let never(..args) = return +#type(never(1, 2)) +``` + +- `include`: `#include "other-file.typ"` + +### Script Styling + +- `set`: + +```typ +#set text(size: 24pt) +四斤鸭梨 +``` + +- `scope`: + +```typ +两只#[兔#set text(fill: rgb("#ffd1dc").darken(15%)) + #[兔白#set text(fill: orange) + 又白],真可爱 +] +``` + +- `set-if`: + +```typ +#let is-dark-theme = true +#set rect(fill: black) if is-dark-theme +#set text(fill: white) if is-dark-theme +#rect([wink!]) +``` + +- `show-set`: + +```typ +#show: set text(fill: blue) +wink! +``` + +- `show`: + +````typ +#show raw: it => it.lines.at(1) +获取代码片段第二行内容:```typ +#{ +set text(fill: true) +} +``` +```` + +- `text-selector`: + +```typ +#show "cpp": strong(emph(box("C++"))) +在古代,cpp是一门常用语言。 +``` + +- `regex-selector`: + +```typ +#show regex("[”。]+"): it => { + set text(font: "KaiTi") + highlight(it, fill: yellow) +} +“无名,万物之始也;有名,万物之母也。” +``` + +- `label-selector`: + +```typ +#show <一整段话>: set text(fill: blue) +#[$lambda$语言是世界上最好的语言。] <一整段话> + +另一段话。 +``` + +- `selector-exp`: + +```typ +#show heading.where(level: 2): set text(fill: blue) += 一级标题 +== 二级标题 +``` + +- `here`: `#context here().position()` +- `here-calc`: `#context [ 页码是偶数:#calc.even(here().page()) ]` +- `query`: `#context query().at(0).body` +- `state`: `#state("my-state", 1)` + +### Script Expressions + +- `func-call`: `#calc.pow(4, 3)` +- `content-param`: `#emph[emphasis]` +- `member-exp`: + +```typ +#`OvO`.text +``` + +- `method-exp`: `#"Hello World".split(" ")` +- `dict-member-exp`: + +```typ +#let cat = (neko-mimi: 2) +#cat.neko-mimi +``` + +- `content-member-exp`: + +```typ +#`OvO`.text +``` + +- `repr`: `#repr[ 一段文本 ]` +- `type`: `#type[一段文本]` +- `eval`: `#type(eval("1"))` +- `eval-markup-mode`: `#eval("== 一个标题", mode: "markup")` +- `array-in`: + +```typ +#let pol = (1, "OvO", []) +#(1 in pol) +``` + +- `array-not-in`: + +```typ +#let pol = (1, "OvO", []) +#([另一段内容] not in pol) +``` + +- `dict-in`: + +```typ +#let cat = (neko-mimi: 2) +#("neko-mimi" in cat) +``` + +- `logical-cmp-exp`: + +```typ +#(1 < 0), #(1 >= 2), +#(1 == 2), #(1 != 2) +``` + +- `logical-calc-exp`: `#(not false), #(false or true), #(true and false)` +- `plus-exp`: `#(+1), #(+0), #(1), #(++1)` +- `minus-exp`: + +```typ +#(-1), #(-0), #(--1), +#(-+-1) +``` + +- `arith-exp`: + +```typ +#(1 + 1), #(1 + -1), +#(1 - 1), #(1 - -1) +``` + +- `assign-exp`: `#let a = 1; #repr(a = 10), #a, #repr(a += 2), #a` +- `string-concat-exp`: `#("a" + "b")` +- `string-mul-exp`: `#("a" * 4), #(4 * "ab")` +- `string-cmp-exp`: `#("a" == "b"), #("a" != "b"), #("a" < "ab"), #("a" >= "a")` +- `int-to-float`: `#float(1), #(type(float(1)))` +- `bool-to-int`: `#int(true), #(type(int(true)))` +- `float-to-int`: `#int(1), #(type(int(1)))` +- `dec-str-to-int`: `#int("1"), #(type(int("1")))` +- `nadec-str-to-int`: + +```typ +#let safe-to-int(x) = { + let res = eval(x) + assert(type(res) == int, message: "should be integer") + res +} +#safe-to-int("0xf"), #(type(safe-to-int("0xf"))) \ +#safe-to-int("0o755"), #(type(safe-to-int("0o755"))) \ +#safe-to-int("0b1011"), #(type(safe-to-int("0b1011"))) \ +``` + +- `num-to-str`: + +```typ +#repr(str(1)), +#repr(str(.5)) +``` + +- `int-to-nadec-str`: `#str(501, base:16), #str(0xdeadbeef, base:36)` +- `bool-to-str`: `#repr(false)` +- `int-to-bool`: + +```typ +#let to-bool(x) = x != 0 +#repr(to-bool(0)), +#repr(to-bool(1)) +``` + + diff --git a/.codex/skills/update-typst-grammar-authoring/SKILL.md b/.codex/skills/update-typst-grammar-authoring/SKILL.md new file mode 100644 index 0000000..6bc2ac2 --- /dev/null +++ b/.codex/skills/update-typst-grammar-authoring/SKILL.md @@ -0,0 +1,62 @@ +--- +name: update-typst-grammar-authoring +description: Use when maintaining the portable typst-grammar-authoring skill from this repository's canonical Typst grammar source. +metadata: + short-description: Regenerate the portable Typst grammar skill +--- + +# Update Typst Grammar Authoring + +Use this skill when a maintainer needs to refresh the portable +`typst-grammar-authoring` skill after changing the canonical grammar examples in +`src/tutorial/reference-grammar.typ`. + +## Inputs And Outputs + +- Canonical source: `src/tutorial/reference-grammar.typ` +- Portable skill: `.codex/skills/typst-grammar-authoring/SKILL.md` +- Traceability artifact: + `.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json` + +## Workflow + +1. Review the source edits in `src/tutorial/reference-grammar.typ` and confirm + they are ready to publish into the portable skill. +2. Regenerate the portable skill body and traceability artifact: + + ```sh + python .codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py + ``` + +3. Review the generated diff for the portable skill and the traceability JSON: + + ```sh + git diff -- .codex/skills/typst-grammar-authoring/SKILL.md .codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json + ``` + +4. Verify the portable skill stayed single-file: + + ```powershell + Get-ChildItem -Force .codex/skills/typst-grammar-authoring + ``` + +5. Verify the portable skill's command examples stayed platform-neutral and did + not reintroduce Windows absolute paths: + + ```sh + rg -n "[A-Za-z]:\\\\|path\\\\to|target\\\\typst-grammar-authoring-check|\\.codex\\\\skills|src\\\\tutorial" .codex/skills/typst-grammar-authoring/SKILL.md + ``` + +6. If the regenerated lookup looks correct, keep the portable skill focused on + author guidance only. Any exact source mapping belongs in the updater-owned + traceability JSON, not in the portable `SKILL.md`. + +## Guardrails + +- Keep the distributed `typst-grammar-authoring` folder limited to `SKILL.md`. +- Treat `.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json` + as the source-mapping artifact for maintainers. +- Keep maintenance-only commands, source-path references, and review steps in + this updater skill rather than the portable one. +- If the generator output reveals a design issue in the portable skill, update + the portable template first and rerun the generator before final review. diff --git a/.codex/skills/update-typst-grammar-authoring/agents/openai.yaml b/.codex/skills/update-typst-grammar-authoring/agents/openai.yaml new file mode 100644 index 0000000..811a648 --- /dev/null +++ b/.codex/skills/update-typst-grammar-authoring/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Update Typst Grammar Authoring" + short_description: "Regenerate the portable Typst grammar skill" + default_prompt: "Use $update-typst-grammar-authoring to refresh the portable Typst grammar skill and its traceability artifacts from src/tutorial/reference-grammar.typ." diff --git a/.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json b/.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json new file mode 100644 index 0000000..ccb6bf9 --- /dev/null +++ b/.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json @@ -0,0 +1,2220 @@ +{ + "source_path": "src/tutorial/reference-grammar.typ", + "category_count": 6, + "entry_count": 127, + "categories": [ + { + "id": "base-elements", + "english_heading": "Base Elements", + "source_heading": "基本元素", + "source_anchor": "grammar-table:base-elements", + "heading_line": 30, + "entries": [ + { + "lookup_key": "paragraph", + "source_label": "段落", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-paragraph" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 33, + "entry_line_end": 39, + "code_line_start": 37, + "code_line_end": 37 + }, + "code": "writing-markup" + }, + { + "lookup_key": "heading", + "source_label": "标题", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-heading" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 40, + "entry_line_end": 46, + "code_line_start": 44, + "code_line_end": 44 + }, + "code": "= Heading" + }, + { + "lookup_key": "heading", + "source_label": "二级标题", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-heading" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 47, + "entry_line_end": 53, + "code_line_start": 51, + "code_line_end": 51 + }, + "code": "== Heading" + }, + { + "lookup_key": "strong", + "source_label": "着重标记", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-strong" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 54, + "entry_line_end": 60, + "code_line_start": 58, + "code_line_end": 58 + }, + "code": "*Strong*" + }, + { + "lookup_key": "emph", + "source_label": "强调标记", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-emph" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 61, + "entry_line_end": 67, + "code_line_start": 65, + "code_line_end": 65 + }, + "code": "_emphasis_" + }, + { + "lookup_key": "emph", + "source_label": "着重且强调标记", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-emph" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 68, + "entry_line_end": 74, + "code_line_start": 72, + "code_line_end": 72 + }, + "code": "*_emphasis_*" + }, + { + "lookup_key": "list", + "source_label": "有序列表", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-list" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 75, + "entry_line_end": 82, + "code_line_start": 79, + "code_line_end": 80 + }, + "code": "+ List 1\n+ List 2" + }, + { + "lookup_key": "continue-list", + "source_label": "有序列表(重新开始标号)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-continue-list" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 83, + "entry_line_end": 90, + "code_line_start": 87, + "code_line_end": 88 + }, + "code": "4. List 1\n+ List 2" + }, + { + "lookup_key": "emum", + "source_label": "无序列表", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-emum" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 91, + "entry_line_end": 98, + "code_line_start": 95, + "code_line_end": 96 + }, + "code": "- Enum 1\n- Enum 2" + }, + { + "lookup_key": "mix-list-emum", + "source_label": "交替有序与无序列表", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-mix-list-emum" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 99, + "entry_line_end": 107, + "code_line_start": 103, + "code_line_end": 105 + }, + "code": "- Enum 1\n + Item 1\n- Enum 2" + }, + { + "lookup_key": "raw", + "source_label": "短代码片段", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 108, + "entry_line_end": 114, + "code_line_start": 112, + "code_line_end": 112 + }, + "code": "`code`" + }, + { + "lookup_key": "long-raw", + "source_label": "长代码片段", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-long-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 115, + "entry_line_end": 121, + "code_line_start": 119, + "code_line_end": 119 + }, + "code": "``` code```" + }, + { + "lookup_key": "lang-raw", + "source_label": "语法高亮", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-lang-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 122, + "entry_line_end": 128, + "code_line_start": 126, + "code_line_end": 126 + }, + "code": "```rs trait World```" + }, + { + "lookup_key": "blocky-raw", + "source_label": "块代码片段", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-blocky-raw" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 129, + "entry_line_end": 137, + "code_line_start": 133, + "code_line_end": 135 + }, + "code": "```typ\n= Heading\n```" + }, + { + "lookup_key": "image", + "source_label": "图像", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-image" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 138, + "entry_line_end": 144, + "code_line_start": 142, + "code_line_end": 142 + }, + "code": "#image(\"/assets/files/香風とうふ店.jpg\", width: 50pt)" + }, + { + "lookup_key": "image-stretch", + "source_label": "拉伸图像", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-image-stretch" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 145, + "entry_line_end": 151, + "code_line_start": 149, + "code_line_end": 149 + }, + "code": "#image(\"/assets/files/香風とうふ店.jpg\", width: 50pt, height: 50pt, fit: \"stretch\")" + }, + { + "lookup_key": "image-inline", + "source_label": "内联图像(盒子法)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-image-inline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 152, + "entry_line_end": 158, + "code_line_start": 156, + "code_line_end": 156 + }, + "code": "在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。" + }, + { + "lookup_key": "figure", + "source_label": "图像标题", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-figure" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 159, + "entry_line_end": 167, + "code_line_start": 163, + "code_line_end": 165 + }, + "code": "#figure(```typ\n#image(\"/assets/files/香風とうふ店.jpg\")\n```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码])" + }, + { + "lookup_key": "link", + "source_label": "链接", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-link" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 168, + "entry_line_end": 174, + "code_line_start": 172, + "code_line_end": 172 + }, + "code": "#link(\"https://zh.wikipedia.org\")[维基百科]" + }, + { + "lookup_key": "http-link", + "source_label": "HTTP(S)链接", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-http-link" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 175, + "entry_line_end": 181, + "code_line_start": 179, + "code_line_end": 179 + }, + "code": "https://zh.wikipedia.org" + }, + { + "lookup_key": "internal-link", + "source_label": "内部链接", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-internal-link" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 182, + "entry_line_end": 189, + "code_line_start": 186, + "code_line_end": 187 + }, + "code": "== 某个标题 \n#link()[链接到某个标题]" + }, + { + "lookup_key": "table", + "source_label": "表格", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-table" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 190, + "entry_line_end": 196, + "code_line_start": 194, + "code_line_end": 194 + }, + "code": "#table(columns: 2, [111], [2], [3])" + }, + { + "lookup_key": "table-align", + "source_label": "对齐表格", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-table-align" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 197, + "entry_line_end": 203, + "code_line_start": 201, + "code_line_end": 201 + }, + "code": "#table(columns: 2, align: center, [111], [2], [3])" + }, + { + "lookup_key": "inline-math", + "source_label": "行内数学公式", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-inline-math" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 204, + "entry_line_end": 210, + "code_line_start": 208, + "code_line_end": 208 + }, + "code": "$sum_x$" + }, + { + "lookup_key": "display-math", + "source_label": "行间数学公式", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-display-math" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 211, + "entry_line_end": 217, + "code_line_start": 215, + "code_line_end": 215 + }, + "code": "$ sum_x $" + }, + { + "lookup_key": "escape-sequences", + "source_label": "标记转义序列", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 218, + "entry_line_end": 224, + "code_line_start": 222, + "code_line_end": 222 + }, + "code": ">\\_<" + }, + { + "lookup_key": "unicode-escape-sequences", + "source_label": "标记的Unicode转义序列", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-unicode-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 225, + "entry_line_end": 231, + "code_line_start": 229, + "code_line_end": 229 + }, + "code": "\\u{9999}" + }, + { + "lookup_key": "newline-by-space", + "source_label": "换行符(转义序列)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-newline-by-space" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 232, + "entry_line_end": 238, + "code_line_start": 236, + "code_line_end": 236 + }, + "code": "A \\ B" + }, + { + "lookup_key": "newline", + "source_label": "换行符情形二", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-newline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 239, + "entry_line_end": 246, + "code_line_start": 243, + "code_line_end": 244 + }, + "code": "A \\\nB" + }, + { + "lookup_key": "shorthand", + "source_label": "速记符", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-shorthand" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 247, + "entry_line_end": 253, + "code_line_start": 251, + "code_line_end": 251 + }, + "code": "北京--上海" + }, + { + "lookup_key": "shorthand-space", + "source_label": "空格(速记符)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-shorthand-space" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 254, + "entry_line_end": 260, + "code_line_start": 258, + "code_line_end": 258 + }, + "code": "A~B" + }, + { + "lookup_key": "inline-comment", + "source_label": "行内注释", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-inline-comment" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 261, + "entry_line_end": 267, + "code_line_start": 265, + "code_line_end": 265 + }, + "code": "// 行内注释" + }, + { + "lookup_key": "cross-line-comment", + "source_label": "行间注释", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-cross-line-comment" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 268, + "entry_line_end": 275, + "code_line_start": 272, + "code_line_end": 273 + }, + "code": "/* 行间注释\n */" + }, + { + "lookup_key": "box", + "source_label": "行内盒子", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-box" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 276, + "entry_line_end": 282, + "code_line_start": 280, + "code_line_end": 280 + }, + "code": "在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。" + } + ] + }, + { + "id": "text-styling", + "english_heading": "Text Styling", + "source_heading": "修饰文本", + "source_anchor": "grammar-table:text", + "heading_line": 285, + "entries": [ + { + "lookup_key": "highlight", + "source_label": "背景高亮", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-highlight" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 288, + "entry_line_end": 294, + "code_line_start": 292, + "code_line_end": 292 + }, + "code": "#highlight[高亮一段内容]" + }, + { + "lookup_key": "underline", + "source_label": "下划线", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-underline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 295, + "entry_line_end": 301, + "code_line_start": 299, + "code_line_end": 299 + }, + "code": "#underline[Language]" + }, + { + "lookup_key": "underline-evade", + "source_label": "无驱逐效果的下划线", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-underline-evade" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 302, + "entry_line_end": 309, + "code_line_start": 306, + "code_line_end": 307 + }, + "code": "#underline(\n evade: false)[ጿኈቼዽ]" + }, + { + "lookup_key": "overline", + "source_label": "上划线", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-overline" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 310, + "entry_line_end": 316, + "code_line_start": 314, + "code_line_end": 314 + }, + "code": "#overline[ጿኈቼዽ]" + }, + { + "lookup_key": "strike", + "source_label": "中划线(删除线)", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-strike" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 317, + "entry_line_end": 323, + "code_line_start": 321, + "code_line_end": 321 + }, + "code": "#strike[ጿኈቼዽ]" + }, + { + "lookup_key": "subscript", + "source_label": "下标", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-subscript" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 324, + "entry_line_end": 330, + "code_line_start": 328, + "code_line_end": 328 + }, + "code": "威严满满#sub[抱头蹲防]" + }, + { + "lookup_key": "superscript", + "source_label": "上标", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-superscript" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 331, + "entry_line_end": 337, + "code_line_start": 335, + "code_line_end": 335 + }, + "code": "香風とうふ店#super[TM]" + }, + { + "lookup_key": "text-size", + "source_label": "设置文本大小", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-text-size" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 338, + "entry_line_end": 344, + "code_line_start": 342, + "code_line_end": 342 + }, + "code": "#text(size: 24pt)[一斤鸭梨]" + }, + { + "lookup_key": "text-fill", + "source_label": "设置文本颜色", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-text-fill" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 345, + "entry_line_end": 351, + "code_line_start": 349, + "code_line_end": 349 + }, + "code": "#text(fill: blue)[蓝色鸭梨]" + }, + { + "lookup_key": "text-font", + "source_label": "设置字体", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-text-font" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 352, + "entry_line_end": 358, + "code_line_start": 356, + "code_line_end": 356 + }, + "code": "#text(font: \"Microsoft YaHei\")[板正鸭梨]" + } + ] + }, + { + "id": "script-declarations", + "english_heading": "Script Declarations", + "source_heading": "脚本声明", + "source_anchor": "grammar-table:decl", + "heading_line": 361, + "entries": [ + { + "lookup_key": "enter-script", + "source_label": "进入脚本模式", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-enter-script" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 364, + "entry_line_end": 370, + "code_line_start": 368, + "code_line_end": 368 + }, + "code": "#1" + }, + { + "lookup_key": "code-block", + "source_label": "代码块", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-code-block" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 371, + "entry_line_end": 377, + "code_line_start": 375, + "code_line_end": 375 + }, + "code": "#{\"a\"; \"b\"}" + }, + { + "lookup_key": "content-block", + "source_label": "内容块", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-content-block" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 378, + "entry_line_end": 384, + "code_line_start": 382, + "code_line_end": 382 + }, + "code": "#[内容块]" + }, + { + "lookup_key": "none-literal", + "source_label": "空字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-none-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 385, + "entry_line_end": 391, + "code_line_start": 389, + "code_line_end": 389 + }, + "code": "#none" + }, + { + "lookup_key": "false-literal", + "source_label": "假(布尔值)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-false-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 392, + "entry_line_end": 398, + "code_line_start": 396, + "code_line_end": 396 + }, + "code": "#false" + }, + { + "lookup_key": "true-literal", + "source_label": "真(布尔值)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-true-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 399, + "entry_line_end": 405, + "code_line_start": 403, + "code_line_end": 403 + }, + "code": "#true" + }, + { + "lookup_key": "integer-literal", + "source_label": "整数字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-integer-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 406, + "entry_line_end": 412, + "code_line_start": 410, + "code_line_end": 410 + }, + "code": "#(-1), #(0), #(1)" + }, + { + "lookup_key": "n-adecimal-literal", + "source_label": "进制数字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-n-adecimal-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 413, + "entry_line_end": 419, + "code_line_start": 417, + "code_line_end": 417 + }, + "code": "#(-0xdeadbeef), #(-0o644), #(-0b1001)" + }, + { + "lookup_key": "float-literal", + "source_label": "浮点数字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-float-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 420, + "entry_line_end": 426, + "code_line_start": 424, + "code_line_end": 424 + }, + "code": "#(0.001), #(.1), #(2.)" + }, + { + "lookup_key": "exp-repr-float", + "source_label": "指数表示法", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-exp-repr-float" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 427, + "entry_line_end": 433, + "code_line_start": 431, + "code_line_end": 431 + }, + "code": "#(1e2), #(1.926e3), #(-1e-3)" + }, + { + "lookup_key": "string-literal", + "source_label": "字符串字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 434, + "entry_line_end": 440, + "code_line_start": 438, + "code_line_end": 438 + }, + "code": "#\"Hello world!!\"" + }, + { + "lookup_key": "str-escape-sequences", + "source_label": "字符串转义序列", + "reference": { + "expression": "refs.scripting-base.with(reference: )", + "module": "scripting-base", + "anchor": "grammar-str-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 441, + "entry_line_end": 447, + "code_line_start": 445, + "code_line_end": 445 + }, + "code": "#\"\\\"\"" + }, + { + "lookup_key": "str-unicode-escape-sequences", + "source_label": "字符串的Unicode转义序列", + "reference": { + "expression": "refs.scripting-base.with(reference: )", + "module": "scripting-base", + "anchor": "grammar-str-unicode-escape-sequences" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 448, + "entry_line_end": 454, + "code_line_start": 452, + "code_line_end": 452 + }, + "code": "#\"\\u{9999}\"" + }, + { + "lookup_key": "array-literal", + "source_label": "数组字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 455, + "entry_line_end": 461, + "code_line_start": 459, + "code_line_end": 459 + }, + "code": "#(1, \"OvO\", [一段内容])" + }, + { + "lookup_key": "dict-literal", + "source_label": "字典字面量", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dict-literal" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 462, + "entry_line_end": 468, + "code_line_start": 466, + "code_line_end": 466 + }, + "code": "#(neko-mimi: 2, \"utterance\": \"喵喵喵\")" + }, + { + "lookup_key": "empty-array", + "source_label": "空数组", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-empty-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 469, + "entry_line_end": 475, + "code_line_start": 473, + "code_line_end": 473 + }, + "code": "#()" + }, + { + "lookup_key": "empty-dict", + "source_label": "空字典", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-empty-dict" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 476, + "entry_line_end": 482, + "code_line_start": 480, + "code_line_end": 480 + }, + "code": "#(:)" + }, + { + "lookup_key": "paren-empty-array", + "source_label": "被括号包裹的空数组", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-paren-empty-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 483, + "entry_line_end": 489, + "code_line_start": 487, + "code_line_end": 487 + }, + "code": "#(())" + }, + { + "lookup_key": "single-member-array", + "source_label": "含有一个元素的数组", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-single-member-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 490, + "entry_line_end": 496, + "code_line_start": 494, + "code_line_end": 494 + }, + "code": "#(1,)" + }, + { + "lookup_key": "var-decl", + "source_label": "变量声明", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-var-decl" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 497, + "entry_line_end": 503, + "code_line_start": 501, + "code_line_end": 501 + }, + "code": "#let x = 1" + }, + { + "lookup_key": "func-decl", + "source_label": "函数声明", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-func-decl" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 504, + "entry_line_end": 510, + "code_line_start": 508, + "code_line_end": 508 + }, + "code": "#let f(x) = x * 2" + }, + { + "lookup_key": "closure", + "source_label": "函数闭包", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-closure" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 511, + "entry_line_end": 517, + "code_line_start": 515, + "code_line_end": 515 + }, + "code": "#let f = (x, y) => x + y" + }, + { + "lookup_key": "named-param", + "source_label": "具名参数声明", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-named-param" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 518, + "entry_line_end": 524, + "code_line_start": 522, + "code_line_end": 522 + }, + "code": "#let g(named: none) = named" + }, + { + "lookup_key": "variadic-param", + "source_label": "含变参函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-variadic-param" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 525, + "entry_line_end": 531, + "code_line_start": 529, + "code_line_end": 529 + }, + "code": "#let g(..args) = args.pos().join([、])" + }, + { + "lookup_key": "destruct-array", + "source_label": "数组解构赋值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-destruct-array" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 532, + "entry_line_end": 538, + "code_line_start": 536, + "code_line_end": 536 + }, + "code": "#let (one, hello-world) = (1, \"Hello, World\")" + }, + { + "lookup_key": "destruct-array-eliminate", + "source_label": "数组解构赋值情形二", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-destruct-array-eliminate" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 539, + "entry_line_end": 545, + "code_line_start": 543, + "code_line_end": 543 + }, + "code": "#let (_, second, ..) = (1, \"Hello, World\", []); #second" + }, + { + "lookup_key": "destruct-dict", + "source_label": "字典解构赋值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-destruct-dict" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 546, + "entry_line_end": 552, + "code_line_start": 550, + "code_line_end": 550 + }, + "code": "#let (neko-mimi: mimi) = (neko-mimi: 2); #mimi" + }, + { + "lookup_key": "array-remapping", + "source_label": "数组内容重映射", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-remapping" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 553, + "entry_line_end": 561, + "code_line_start": 557, + "code_line_end": 559 + }, + "code": "#let (a, b, c) = (1, 2, 3)\n#let (b, c, a) = (a, b, c)\n#a, #b, #c" + }, + { + "lookup_key": "array-swap", + "source_label": "数组内容交换", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-swap" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 562, + "entry_line_end": 570, + "code_line_start": 566, + "code_line_end": 568 + }, + "code": "#let (a, b) = (1, 2)\n#((a, b) = (b, a))\n#a, #b" + }, + { + "lookup_key": "placeholder", + "source_label": "占位符(`let _ = ..`)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-placeholder" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 571, + "entry_line_end": 581, + "code_line_start": 575, + "code_line_end": 579 + }, + "code": "#let last-two(t) = {\n let _ = t.pop()\n t.pop()\n}\n#last-two((1, 2, 3, 4))" + } + ] + }, + { + "id": "script-statements", + "english_heading": "Script Statements", + "source_heading": "脚本语句", + "source_anchor": null, + "heading_line": 584, + "entries": [ + { + "lookup_key": "if", + "source_label": "`if`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-if" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 587, + "entry_line_end": 594, + "code_line_start": 591, + "code_line_end": 592 + }, + "code": "#if true { 1 },\n#if false { 1 } else { 0 }" + }, + { + "lookup_key": "if-if", + "source_label": "串联`if`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-if-if" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 595, + "entry_line_end": 602, + "code_line_start": 599, + "code_line_end": 600 + }, + "code": "#if false { 0 } else if true { 1 },\n#if false { 2 } else if false { 1 } else { 0 }" + }, + { + "lookup_key": "while", + "source_label": "`while`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-while" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 603, + "entry_line_end": 615, + "code_line_start": 607, + "code_line_end": 613 + }, + "code": "#{\n let i = 0;\n while i < 10 {\n (i * 2, )\n i += 1;\n }\n}" + }, + { + "lookup_key": "for", + "source_label": "`for`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-for" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 616, + "entry_line_end": 624, + "code_line_start": 620, + "code_line_end": 622 + }, + "code": "#for i in range(10) {\n (i * 2, )\n}" + }, + { + "lookup_key": "for-destruct", + "source_label": "`for`语句解构赋值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-for-destruct" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 625, + "entry_line_end": 631, + "code_line_start": 629, + "code_line_end": 629 + }, + "code": "#for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\\ ]" + }, + { + "lookup_key": "break", + "source_label": "`break`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-break" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 632, + "entry_line_end": 638, + "code_line_start": 636, + "code_line_end": 636 + }, + "code": "#for i in range(10) { (i, ); (i + 1926, ); break }" + }, + { + "lookup_key": "continue", + "source_label": "`continue`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-continue" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 639, + "entry_line_end": 648, + "code_line_start": 643, + "code_line_end": 646 + }, + "code": "#for i in range(10) {\n if calc.even(i) { continue }\n (i, )\n}" + }, + { + "lookup_key": "return", + "source_label": "`return`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-return" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 649, + "entry_line_end": 656, + "code_line_start": 653, + "code_line_end": 654 + }, + "code": "#let never(..args) = return\n#type(never(1, 2))" + }, + { + "lookup_key": "include", + "source_label": "`include`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-include" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 657, + "entry_line_end": 663, + "code_line_start": 661, + "code_line_end": 661 + }, + "code": "#include \"other-file.typ\"" + } + ] + }, + { + "id": "script-styling", + "english_heading": "Script Styling", + "source_heading": "脚本样式", + "source_anchor": null, + "heading_line": 666, + "entries": [ + { + "lookup_key": "set", + "source_label": "`set`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-set" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 669, + "entry_line_end": 676, + "code_line_start": 673, + "code_line_end": 674 + }, + "code": "#set text(size: 24pt)\n四斤鸭梨" + }, + { + "lookup_key": "scope", + "source_label": "作用域", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-scope" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 677, + "entry_line_end": 686, + "code_line_start": 681, + "code_line_end": 684 + }, + "code": "两只#[兔#set text(fill: rgb(\"#ffd1dc\").darken(15%))\n #[兔白#set text(fill: orange)\n 又白],真可爱\n]" + }, + { + "lookup_key": "set-if", + "source_label": "`set if`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-set-if" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 687, + "entry_line_end": 696, + "code_line_start": 691, + "code_line_end": 694 + }, + "code": "#let is-dark-theme = true\n#set rect(fill: black) if is-dark-theme\n#set text(fill: white) if is-dark-theme\n#rect([wink!])" + }, + { + "lookup_key": "show-set", + "source_label": "`show set`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-show-set" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 697, + "entry_line_end": 704, + "code_line_start": 701, + "code_line_end": 702 + }, + "code": "#show: set text(fill: blue)\nwink!" + }, + { + "lookup_key": "show", + "source_label": "`show`语句", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-show" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 705, + "entry_line_end": 716, + "code_line_start": 709, + "code_line_end": 714 + }, + "code": "#show raw: it => it.lines.at(1)\n获取代码片段第二行内容:```typ\n#{\nset text(fill: true)\n}\n```" + }, + { + "lookup_key": "text-selector", + "source_label": "文本选择器", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-text-selector" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 717, + "entry_line_end": 724, + "code_line_start": 721, + "code_line_end": 722 + }, + "code": "#show \"cpp\": strong(emph(box(\"C++\")))\n在古代,cpp是一门常用语言。" + }, + { + "lookup_key": "regex-selector", + "source_label": "正则文本选择器", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-regex-selector" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 725, + "entry_line_end": 735, + "code_line_start": 729, + "code_line_end": 733 + }, + "code": "#show regex(\"[”。]+\"): it => {\n set text(font: \"KaiTi\")\n highlight(it, fill: yellow)\n}\n“无名,万物之始也;有名,万物之母也。”" + }, + { + "lookup_key": "label-selector", + "source_label": "标签选择器", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-label-selector" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 736, + "entry_line_end": 745, + "code_line_start": 740, + "code_line_end": 743 + }, + "code": "#show <一整段话>: set text(fill: blue)\n#[$lambda$语言是世界上最好的语言。] <一整段话>\n\n另一段话。" + }, + { + "lookup_key": "selector-exp", + "source_label": "选择器表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-selector-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 746, + "entry_line_end": 754, + "code_line_start": 750, + "code_line_end": 752 + }, + "code": "#show heading.where(level: 2): set text(fill: blue)\n= 一级标题\n== 二级标题" + }, + { + "lookup_key": "here", + "source_label": "获取位置", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-here" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 755, + "entry_line_end": 761, + "code_line_start": 759, + "code_line_end": 759 + }, + "code": "#context here().position()" + }, + { + "lookup_key": "here-calc", + "source_label": "检测当前页面是否为偶数页(位置相关计算)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-here-calc" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 762, + "entry_line_end": 768, + "code_line_start": 766, + "code_line_end": 766 + }, + "code": "#context [ 页码是偶数:#calc.even(here().page()) ]" + }, + { + "lookup_key": "query", + "source_label": "查询文档内容", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-query" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 769, + "entry_line_end": 775, + "code_line_start": 773, + "code_line_end": 773 + }, + "code": "#context query().at(0).body" + }, + { + "lookup_key": "state", + "source_label": "声明全局状态", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-state" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 776, + "entry_line_end": 782, + "code_line_start": 780, + "code_line_end": 780 + }, + "code": "#state(\"my-state\", 1)" + } + ] + }, + { + "id": "script-expressions", + "english_heading": "Script Expressions", + "source_heading": "脚本表达式", + "source_anchor": null, + "heading_line": 785, + "entries": [ + { + "lookup_key": "func-call", + "source_label": "函数调用", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-func-call" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 788, + "entry_line_end": 794, + "code_line_start": 792, + "code_line_end": 792 + }, + "code": "#calc.pow(4, 3)" + }, + { + "lookup_key": "content-param", + "source_label": "函数调用传递内容参数", + "reference": { + "expression": "refs.writing-markup.with(reference: )", + "module": "writing-markup", + "anchor": "grammar-content-param" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 795, + "entry_line_end": 801, + "code_line_start": 799, + "code_line_end": 799 + }, + "code": "#emph[emphasis]" + }, + { + "lookup_key": "member-exp", + "source_label": "成员访问", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-member-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 802, + "entry_line_end": 808, + "code_line_start": 806, + "code_line_end": 806 + }, + "code": "#`OvO`.text" + }, + { + "lookup_key": "method-exp", + "source_label": "方法调用", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-method-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 809, + "entry_line_end": 815, + "code_line_start": 813, + "code_line_end": 813 + }, + "code": "#\"Hello World\".split(\" \")" + }, + { + "lookup_key": "dict-member-exp", + "source_label": "字典成员访问", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dict-member-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 816, + "entry_line_end": 823, + "code_line_start": 820, + "code_line_end": 821 + }, + "code": "#let cat = (neko-mimi: 2)\n#cat.neko-mimi" + }, + { + "lookup_key": "content-member-exp", + "source_label": "内容成员访问", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-content-member-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 824, + "entry_line_end": 830, + "code_line_start": 828, + "code_line_end": 828 + }, + "code": "#`OvO`.text" + }, + { + "lookup_key": "repr", + "source_label": "代码表示的自省函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-repr" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 831, + "entry_line_end": 837, + "code_line_start": 835, + "code_line_end": 835 + }, + "code": "#repr[ 一段文本 ]" + }, + { + "lookup_key": "type", + "source_label": "类型的自省函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-type" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 838, + "entry_line_end": 844, + "code_line_start": 842, + "code_line_end": 842 + }, + "code": "#type[一段文本]" + }, + { + "lookup_key": "eval", + "source_label": "求值函数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-eval" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 845, + "entry_line_end": 851, + "code_line_start": 849, + "code_line_end": 849 + }, + "code": "#type(eval(\"1\"))" + }, + { + "lookup_key": "eval-markup-mode", + "source_label": "求值函数(标记模式)", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-eval-markup-mode" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 852, + "entry_line_end": 858, + "code_line_start": 856, + "code_line_end": 856 + }, + "code": "#eval(\"== 一个标题\", mode: \"markup\")" + }, + { + "lookup_key": "array-in", + "source_label": "判断数组内容", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-in" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 859, + "entry_line_end": 866, + "code_line_start": 863, + "code_line_end": 864 + }, + "code": "#let pol = (1, \"OvO\", [])\n#(1 in pol)" + }, + { + "lookup_key": "array-not-in", + "source_label": "判断数组内容不在", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-array-not-in" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 867, + "entry_line_end": 874, + "code_line_start": 871, + "code_line_end": 872 + }, + "code": "#let pol = (1, \"OvO\", [])\n#([另一段内容] not in pol)" + }, + { + "lookup_key": "dict-in", + "source_label": "判断字典内容", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dict-in" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 875, + "entry_line_end": 882, + "code_line_start": 879, + "code_line_end": 880 + }, + "code": "#let cat = (neko-mimi: 2)\n#(\"neko-mimi\" in cat)" + }, + { + "lookup_key": "logical-cmp-exp", + "source_label": "逻辑比较表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-logical-cmp-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 883, + "entry_line_end": 890, + "code_line_start": 887, + "code_line_end": 888 + }, + "code": "#(1 < 0), #(1 >= 2),\n#(1 == 2), #(1 != 2)" + }, + { + "lookup_key": "logical-calc-exp", + "source_label": "逻辑运算表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-logical-calc-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 891, + "entry_line_end": 897, + "code_line_start": 895, + "code_line_end": 895 + }, + "code": "#(not false), #(false or true), #(true and false)" + }, + { + "lookup_key": "plus-exp", + "source_label": "取正运算", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-plus-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 898, + "entry_line_end": 904, + "code_line_start": 902, + "code_line_end": 902 + }, + "code": "#(+1), #(+0), #(1), #(++1)" + }, + { + "lookup_key": "minus-exp", + "source_label": "取负运算", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-minus-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 905, + "entry_line_end": 912, + "code_line_start": 909, + "code_line_end": 910 + }, + "code": "#(-1), #(-0), #(--1),\n#(-+-1)" + }, + { + "lookup_key": "arith-exp", + "source_label": "算术运算表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-arith-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 913, + "entry_line_end": 920, + "code_line_start": 917, + "code_line_end": 918 + }, + "code": "#(1 + 1), #(1 + -1),\n#(1 - 1), #(1 - -1)" + }, + { + "lookup_key": "assign-exp", + "source_label": "赋值表达式", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-assign-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 921, + "entry_line_end": 927, + "code_line_start": 925, + "code_line_end": 925 + }, + "code": "#let a = 1; #repr(a = 10), #a, #repr(a += 2), #a" + }, + { + "lookup_key": "string-concat-exp", + "source_label": "字符串连接", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-concat-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 928, + "entry_line_end": 934, + "code_line_start": 932, + "code_line_end": 932 + }, + "code": "#(\"a\" + \"b\")" + }, + { + "lookup_key": "string-mul-exp", + "source_label": "重复连接字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-mul-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 935, + "entry_line_end": 941, + "code_line_start": 939, + "code_line_end": 939 + }, + "code": "#(\"a\" * 4), #(4 * \"ab\")" + }, + { + "lookup_key": "string-cmp-exp", + "source_label": "字符串比较", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-string-cmp-exp" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 942, + "entry_line_end": 948, + "code_line_start": 946, + "code_line_end": 946 + }, + "code": "#(\"a\" == \"b\"), #(\"a\" != \"b\"), #(\"a\" < \"ab\"), #(\"a\" >= \"a\")" + }, + { + "lookup_key": "int-to-float", + "source_label": "整数转浮点数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-int-to-float" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 949, + "entry_line_end": 955, + "code_line_start": 953, + "code_line_end": 953 + }, + "code": "#float(1), #(type(float(1)))" + }, + { + "lookup_key": "bool-to-int", + "source_label": "布尔值转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-bool-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 956, + "entry_line_end": 962, + "code_line_start": 960, + "code_line_end": 960 + }, + "code": "#int(true), #(type(int(true)))" + }, + { + "lookup_key": "float-to-int", + "source_label": "浮点数转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-float-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 963, + "entry_line_end": 969, + "code_line_start": 967, + "code_line_end": 967 + }, + "code": "#int(1), #(type(int(1)))" + }, + { + "lookup_key": "dec-str-to-int", + "source_label": "十进制字符串转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-dec-str-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 970, + "entry_line_end": 976, + "code_line_start": 974, + "code_line_end": 974 + }, + "code": "#int(\"1\"), #(type(int(\"1\")))" + }, + { + "lookup_key": "nadec-str-to-int", + "source_label": "十六进制/八进制/二进制字符串转整数", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-nadec-str-to-int" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 977, + "entry_line_end": 990, + "code_line_start": 981, + "code_line_end": 988 + }, + "code": "#let safe-to-int(x) = {\n let res = eval(x)\n assert(type(res) == int, message: \"should be integer\")\n res\n}\n#safe-to-int(\"0xf\"), #(type(safe-to-int(\"0xf\"))) \\\n#safe-to-int(\"0o755\"), #(type(safe-to-int(\"0o755\"))) \\\n#safe-to-int(\"0b1011\"), #(type(safe-to-int(\"0b1011\"))) \\" + }, + { + "lookup_key": "num-to-str", + "source_label": "数字转字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-num-to-str" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 991, + "entry_line_end": 998, + "code_line_start": 995, + "code_line_end": 996 + }, + "code": "#repr(str(1)),\n#repr(str(.5))" + }, + { + "lookup_key": "int-to-nadec-str", + "source_label": "整数转十六进制字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-int-to-nadec-str" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 999, + "entry_line_end": 1005, + "code_line_start": 1003, + "code_line_end": 1003 + }, + "code": "#str(501, base:16), #str(0xdeadbeef, base:36)" + }, + { + "lookup_key": "bool-to-str", + "source_label": "布尔值转字符串", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-bool-to-str" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 1006, + "entry_line_end": 1012, + "code_line_start": 1010, + "code_line_end": 1010 + }, + "code": "#repr(false)" + }, + { + "lookup_key": "int-to-bool", + "source_label": "数字转布尔值", + "reference": { + "expression": "refs.content-scope-style.with(reference: )", + "module": "content-scope-style", + "anchor": "grammar-int-to-bool" + }, + "source": { + "path": "src/tutorial/reference-grammar.typ", + "entry_line_start": 1013, + "entry_line_end": 1021, + "code_line_start": 1017, + "code_line_end": 1019 + }, + "code": "#let to-bool(x) = x != 0\n#repr(to-bool(0)),\n#repr(to-bool(1))" + } + ] + } + ] +} diff --git a/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py new file mode 100644 index 0000000..fdcfc1f --- /dev/null +++ b/.codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +"""Generate Typst grammar data and sync it into the portable skill.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +import textwrap +from collections import OrderedDict +from pathlib import Path + + +CATEGORY_MAP = { + "基本元素": { + "id": "base-elements", + "title": "Base Elements", + }, + "修饰文本": { + "id": "text-styling", + "title": "Text Styling", + }, + "脚本声明": { + "id": "script-declarations", + "title": "Script Declarations", + }, + "脚本语句": { + "id": "script-statements", + "title": "Script Statements", + }, + "脚本样式": { + "id": "script-styling", + "title": "Script Styling", + }, + "脚本表达式": { + "id": "script-expressions", + "title": "Script Expressions", + }, +} + +CATEGORY_PATTERN = re.compile( + r"^==\s+分类:(?P.+?)(?:\s+<(?P[^>]+)>)?\s*$", + re.MULTILINE, +) + +ENTRY_PATTERN = re.compile( + r""" + \(\s*\n + \s*\[(?P.*?)\],\s*\n + \s*(?Prefs\.(?P[\w-]+)\.with\(reference:\s*<(?P[^>]+)>\)),\s*\n + \s*`{3,4}typ\s*\n + (?P.*?) + \n\s*`{3,4},\s*\n + \s*\), + """, + re.DOTALL | re.VERBOSE, +) + +GENERATED_BEGIN = "" +GENERATED_END = "" +INLINE_CODE_LIMIT = 88 + + +def line_number(text: str, offset: int) -> int: + return text.count("\n", 0, offset) + 1 + + +def normalize_code(raw_code: str) -> str: + return textwrap.dedent(raw_code).strip("\n") + + +def lookup_key(reference_anchor: str) -> str: + prefix = "grammar-" + if reference_anchor.startswith(prefix): + return reference_anchor[len(prefix) :] + return reference_anchor + + +def repo_relative(path: Path, repo_root: Path) -> str: + try: + return Path(os.path.relpath(path.resolve(), repo_root.resolve())).as_posix() + except ValueError: + return path.resolve().as_posix() + + +def parse_categories( + source_text: str, source_path: Path, repo_root: Path +) -> list[dict]: + categories = [] + category_matches = list(CATEGORY_PATTERN.finditer(source_text)) + if not category_matches: + raise ValueError(f"No categories found in {source_path}") + + for index, match in enumerate(category_matches): + section_start = match.end() + section_end = ( + category_matches[index + 1].start() + if index + 1 < len(category_matches) + else len(source_text) + ) + section_text = source_text[section_start:section_end] + source_heading = match.group("source_heading") + english_heading = CATEGORY_MAP.get(source_heading, {}).get( + "title", source_heading + ) + category_id = CATEGORY_MAP.get(source_heading, {}).get( + "id", + f"category-{index + 1}", + ) + category = { + "id": category_id, + "english_heading": english_heading, + "source_heading": source_heading, + "source_anchor": match.group("source_anchor"), + "heading_line": line_number(source_text, match.start()), + "entries": [], + } + + for entry_match in ENTRY_PATTERN.finditer(section_text): + absolute_start = section_start + entry_match.start() + absolute_end = section_start + entry_match.end() + code_start = section_start + entry_match.start("code") + code_end = section_start + entry_match.end("code") + entry = { + "lookup_key": lookup_key(entry_match.group("reference_anchor")), + "source_label": entry_match.group("source_label").strip(), + "reference": { + "expression": entry_match.group("reference_expr").strip(), + "module": entry_match.group("reference_module"), + "anchor": entry_match.group("reference_anchor"), + }, + "source": { + "path": repo_relative(source_path, repo_root), + "entry_line_start": line_number(source_text, absolute_start), + "entry_line_end": line_number(source_text, absolute_end), + "code_line_start": line_number(source_text, code_start), + "code_line_end": line_number(source_text, code_end), + }, + "code": normalize_code(entry_match.group("code")), + } + category["entries"].append(entry) + + if not category["entries"]: + raise ValueError( + f"No entries parsed for category '{source_heading}' in {source_path}" + ) + + categories.append(category) + + return categories + + +def build_catalog(source_path: Path, repo_root: Path) -> dict: + source_text = source_path.read_text(encoding="utf-8") + categories = parse_categories(source_text, source_path, repo_root) + return { + "source_path": repo_relative(source_path, repo_root), + "category_count": len(categories), + "entry_count": sum(len(category["entries"]) for category in categories), + "categories": categories, + } + + +def is_inline_safe(code: str) -> bool: + return "\n" not in code and "`" not in code and len(code) <= INLINE_CODE_LIMIT + + +def grouped_entries(entries: list[dict]) -> list[dict]: + groups: OrderedDict[str, list[str]] = OrderedDict() + for entry in entries: + groups.setdefault(entry["lookup_key"], []) + if entry["code"] not in groups[entry["lookup_key"]]: + groups[entry["lookup_key"]].append(entry["code"]) + return [ + { + "lookup_key": lookup_key, + "examples": examples, + } + for lookup_key, examples in groups.items() + ] + + +def render_example_block(code: str) -> list[str]: + return [ + "```typ", + code, + "```", + ] + + +def render_skill_lookup(catalog: dict) -> str: + lines: list[str] = [] + for category in catalog["categories"]: + lines.extend( + [ + f"### {category['english_heading']}", + "", + ] + ) + for group in grouped_entries(category["entries"]): + inline_examples = [ + code for code in group["examples"] if is_inline_safe(code) + ] + if len(inline_examples) == len(group["examples"]): + joined = "; ".join(f"`{code}`" for code in inline_examples) + lines.append(f"- `{group['lookup_key']}`: {joined}") + continue + + lines.append(f"- `{group['lookup_key']}`:") + lines.append("") + for index, code in enumerate(group["examples"]): + lines.extend(render_example_block(code)) + if index != len(group["examples"]) - 1: + lines.append("") + lines.append("") + + while lines and not lines[-1]: + lines.pop() + return "\n".join(lines) + "\n" + + +def update_skill(skill_path: Path, generated_lookup: str) -> None: + skill_text = skill_path.read_text(encoding="utf-8") + pattern = re.compile( + rf"{re.escape(GENERATED_BEGIN)}(?P\r?\n).*?{re.escape(GENERATED_END)}", + re.DOTALL, + ) + + def _replacement(match: re.Match[str]) -> str: + newline = match.group("newline") + return f"{GENERATED_BEGIN}{newline}{generated_lookup}{GENERATED_END}" + + updated_text, count = pattern.subn(_replacement, skill_text, count=1) + if count != 1: + raise ValueError(f"Could not find generated section markers in {skill_path}") + skill_path.write_text(updated_text, encoding="utf-8") + + +def write_outputs( + catalog: dict, + output_dir: Path, + skill_path: Path, +) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + json_path = output_dir / "grammar-catalog.json" + json_path.write_text( + json.dumps(catalog, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + update_skill(skill_path, render_skill_lookup(catalog)) + print(f"[OK] Wrote {json_path}") + print(f"[OK] Updated {skill_path}") + + +def default_source(script_path: Path) -> Path: + return script_path.parents[4] / "src" / "tutorial" / "reference-grammar.typ" + + +def default_skill_path(script_path: Path) -> Path: + return script_path.parents[2] / "typst-grammar-authoring" / "SKILL.md" + + +def main() -> int: + script_path = Path(__file__).resolve() + parser = argparse.ArgumentParser( + description="Generate Typst grammar data from reference-grammar.typ.", + ) + parser.add_argument( + "--source", + type=Path, + default=default_source(script_path), + help="Path to src/tutorial/reference-grammar.typ", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=script_path.parent.parent / "references", + help="Directory for generated reference files", + ) + parser.add_argument( + "--skill-path", + type=Path, + default=default_skill_path(script_path), + help="Path to the portable SKILL.md file to sync", + ) + args = parser.parse_args() + + try: + repo_root = script_path.parents[4] + source_path = args.source.resolve() + catalog = build_catalog(source_path, repo_root) + write_outputs( + catalog, + args.output_dir.resolve(), + args.skill_path.resolve(), + ) + except Exception as exc: # pragma: no cover - CLI error surface + print(f"[ERROR] {exc}", file=sys.stderr) + return 1 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/.openspec.yaml b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/.openspec.yaml new file mode 100644 index 0000000..49ccc67 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-14 diff --git a/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/design.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/design.md new file mode 100644 index 0000000..1cf9b29 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/design.md @@ -0,0 +1,155 @@ +## Context + +This repository contains a detailed Typst grammar catalog in +`src/tutorial/reference-grammar.typ`. The original change turned that tutorial +asset into a Codex skill, but the first implementation blended portable author +guidance with repo-local maintenance machinery. As a result, +`typst-grammar-authoring` now includes updater scripts, traceability artifacts, +and path examples that are not ideal for copying into another repository. + +The revised direction is to separate those responsibilities: + +- `typst-grammar-authoring` is the portable skill that authors use. +- `update-typst-grammar-authoring` is the repo-local maintenance skill that + updates the portable one from canonical tutorial examples. + +## Goals / Non-Goals + +**Goals:** +- Keep `typst-grammar-authoring` as a self-contained, English-only, + distributable skill with only `SKILL.md`. +- Keep grammar lookup and validation workflows available inside the portable + `SKILL.md`. +- Move generator scripts, traceability data, and repo-specific maintenance + instructions into `update-typst-grammar-authoring`. +- Eliminate Windows-specific command paths and other platform-specific path + examples from the distributed `SKILL.md`. +- Preserve the ability to regenerate the portable skill from + `src/tutorial/reference-grammar.typ`. + +**Non-Goals:** +- Translate the full Chinese tutorial into English. +- Replace Typst's own compiler diagnostics with a custom parser or validator. +- Depend on Tinymist, VS Code, or any specific editor integration for required + validation. +- Create a package manager or external publishing workflow for distributing the + portable skill. + +## Decisions + +### Decision: Split authoring and maintenance into two skills + +The repository will contain two skills with different responsibilities: + +- `typst-grammar-authoring` for portable author guidance +- `update-typst-grammar-authoring` for repo-local regeneration and maintenance + +Why this approach: +- It keeps the distributed skill easy to copy into other repositories. +- It lets the updater skill retain repo-specific scripts and source references + without polluting the authoring skill. +- It makes it clearer which instructions are for end users versus maintainers. + +Alternative considered: +- Keep one repo-local skill with embedded updater logic. Rejected because it + mixes portability and maintenance concerns in the same artifact. + +### Decision: Make the portable skill a single-file skill + +`typst-grammar-authoring` will consist only of `SKILL.md`. It must not depend +on sibling scripts, references, or optional UI metadata to function after being +copied to another repository. + +Why this approach: +- A single file is the easiest form to distribute and review. +- It minimizes accidental coupling to repo-local layout and helper artifacts. +- It keeps the authoring experience simple for downstream repositories. + +Alternative considered: +- Keep `agents/`, `references/`, or `scripts/` alongside the distributed skill. + Rejected because the user explicitly wants the authoring skill to remain only + a Markdown file. + +### Decision: Put all repo-specific updater assets under `update-typst-grammar-authoring` + +The updater skill will own: + +- regeneration scripts +- traceability artifacts +- repo-specific instructions that mention `src/tutorial/reference-grammar.typ` +- any maintenance-only metadata + +Why this approach: +- It centralizes maintenance behavior in one place. +- It keeps downstream consumers from carrying unnecessary repo-specific files. +- It preserves exact source traceability without expanding the portable skill + context. + +Alternative considered: +- Leave traceability JSON and generator scripts under the portable skill folder. + Rejected because those files are maintenance artifacts, not distributed + authoring guidance. + +### Decision: Use platform-neutral path examples in the portable skill + +The distributed `SKILL.md` will use forward-slash, repo-relative, or placeholder +paths such as `path/to/document.typ` and `target/typst-grammar-authoring-check/document.html`. +It must not use Windows absolute paths or Windows-style backslash-only command +examples. + +Why this approach: +- It keeps the skill readable across operating systems. +- It avoids downstream users mistaking repo-local Windows commands for required + syntax. +- It makes the portable skill genuinely portable instead of just copyable. + +Alternative considered: +- Keep PowerShell-oriented backslash paths because the repo is currently being + edited on Windows. Rejected because the distributed skill should not inherit a + single machine's path style. + +### Decision: Keep the portable skill self-contained, but keep verbose traceability out of it + +The portable skill will still embed a compact grammar lookup and validation +workflow inside `SKILL.md`, but verbose canonical references and source-line +traceability will live in updater-owned artifacts instead. + +Why this approach: +- It preserves low-context usability for authors. +- It keeps the portable skill smaller than a fully traceable catalog. +- It still gives maintainers a way to audit generated entries precisely. + +Alternative considered: +- Remove all generated grammar data from the portable skill and require a sidecar + reference file. Rejected because the portable skill must remain single-file. + +## Risks / Trade-offs + +- A single-file portable skill is still larger than a minimal skill because it + embeds grammar lookup content. → Mitigation: keep examples compact and keep + verbose traceability in the updater skill only. +- Splitting the skills adds another artifact to maintain. → Mitigation: make the + updater skill the clear owner of regeneration and validation steps. +- Portable path examples may drift back toward platform-specific syntax during + updates. → Mitigation: add explicit tasks and verification for path style. + +## Migration Plan + +1. Create a repo-local `update-typst-grammar-authoring` skill under + `.codex/skills/`. +2. Move generator scripts, traceability artifacts, and repo-specific update + instructions into the updater skill. +3. Reduce `.codex/skills/typst-grammar-authoring/` to a single distributable + `SKILL.md`. +4. Rewrite distributed validation commands and examples to use platform-neutral + paths. +5. Verify the updater still regenerates the distributed skill correctly. + +Rollback strategy: +- Restore the previous single-skill folder layout if the split causes an + unacceptable maintenance burden. + +## Open Questions + +- None at proposal update time. The split between portable and maintenance + skills is the intended direction. diff --git a/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/proposal.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/proposal.md new file mode 100644 index 0000000..77cd420 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/proposal.md @@ -0,0 +1,53 @@ +## Why + +The current `typst-grammar-authoring` implementation mixes two different jobs: +portable Typst authoring guidance for end users and repo-local maintenance +logic for regenerating that guidance from `src/tutorial/reference-grammar.typ`. +That coupling makes the distributed skill harder to copy into another +repository, and it has already allowed Windows-style command paths and +repo-specific maintenance details to leak into `SKILL.md`. + +We want to split those concerns cleanly: + +- `typst-grammar-authoring` should become the portable skill that users can copy + to another repository as a single `SKILL.md` file. +- `update-typst-grammar-authoring` should become the repo-local maintenance + skill that regenerates and validates the portable skill from this tutorial's + canonical grammar source. + +## What Changes + +- Split the current single skill implementation into two separate skills: + `typst-grammar-authoring` and `update-typst-grammar-authoring`. +- Make `typst-grammar-authoring` a self-contained, English-only, distributable + skill consisting only of `SKILL.md`. +- Move generator scripts, traceability artifacts, repo-specific update + instructions, and any maintenance-only metadata into + `update-typst-grammar-authoring`. +- Require the distributed `typst-grammar-authoring/SKILL.md` to use + platform-neutral path examples instead of Windows-specific command paths. +- Keep compile, HTML, and SVG validation guidance in the portable skill while + keeping verbose traceability data outside its default context. + +## Capabilities + +### New Capabilities +- `typst-document-authoring-skill`: Provide a portable, single-file Typst + authoring skill with embedded grammar lookup and compile-based validation + guidance. +- `typst-document-authoring-skill-maintenance`: Provide a repo-local updater + skill that regenerates the portable Typst authoring skill from + `src/tutorial/reference-grammar.typ`. + +### Modified Capabilities +- None. + +## Impact + +- Affects repo-local skill content under `.codex/skills/`. +- Changes `typst-grammar-authoring` from a repo-local folder with support files + into a portable single-file skill. +- Adds or updates a separate `update-typst-grammar-authoring` skill for + generation, validation, and traceability workflows. +- Removes Windows-specific command path examples from the distributed authoring + skill. diff --git a/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md new file mode 100644 index 0000000..18c5197 --- /dev/null +++ b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md @@ -0,0 +1,92 @@ +## ADDED Requirements + +### Requirement: Portable authoring skill is English-facing and self-contained +The distributed `typst-grammar-authoring` skill SHALL be complete as a +single-file, English-facing `SKILL.md` so it can be copied into another +repository without requiring sibling support files. + +#### Scenario: Single-file portable distribution +- **WHEN** `typst-grammar-authoring` is prepared for downstream use +- **THEN** the skill folder MUST be complete with only `SKILL.md` +- **AND** that `SKILL.md` MUST contain the authoring guidance, grammar lookup, + and validation workflow needed by the consuming repository + +### Requirement: Portable authoring skill avoids Windows-specific path examples +The distributed `typst-grammar-authoring/SKILL.md` SHALL use platform-neutral, +forward-slash, or placeholder filesystem paths in commands and examples. + +#### Scenario: Portable command examples +- **WHEN** `typst-grammar-authoring/SKILL.md` is authored or regenerated +- **THEN** command examples MUST use repo-relative or placeholder paths such as + `path/to/document.typ` +- **AND** the file MUST NOT include Windows absolute paths or Windows-style + backslash-only command paths + +### Requirement: Portable authoring skill embeds compact grammar lookup +The distributed `typst-grammar-authoring/SKILL.md` SHALL embed a compact Typst +grammar lookup derived from `src/tutorial/reference-grammar.typ` so it remains +single-file while still teaching canonical syntax patterns. + +#### Scenario: Grammar lookup without sidecar references +- **WHEN** Codex needs a Typst syntax pattern while using + `typst-grammar-authoring` +- **THEN** the skill MUST provide that lookup inside `SKILL.md` +- **AND** the lookup MUST be derived from canonical examples in + `src/tutorial/reference-grammar.typ` + +### Requirement: Updater skill owns regeneration of the portable skill +The repo-local `update-typst-grammar-authoring` skill SHALL own the workflow +that regenerates the distributed `typst-grammar-authoring/SKILL.md` from +`src/tutorial/reference-grammar.typ`. + +#### Scenario: Repo-local regeneration +- **WHEN** a maintainer updates canonical grammar examples from + `src/tutorial/reference-grammar.typ` +- **THEN** `update-typst-grammar-authoring` MUST provide the update workflow +- **AND** that workflow MUST produce the distributed + `typst-grammar-authoring/SKILL.md` + +### Requirement: Updater skill preserves verbose traceability outside portable context +The updater workflow SHALL preserve exact source traceability in separate +maintenance artifacts without injecting verbose canonical reference and +traceability sections into the distributed `typst-grammar-authoring/SKILL.md`. + +#### Scenario: Maintainer needs exact source mapping +- **WHEN** a maintainer needs exact source mapping for a generated grammar entry +- **THEN** `update-typst-grammar-authoring` MUST provide a separate + traceability artifact +- **AND** the distributed `typst-grammar-authoring/SKILL.md` MUST omit verbose + canonical reference and traceability sections from its active context + +### Requirement: Grammar validation uses Typst compilation +The portable authoring skill SHALL define `typst compile` as the required +validation workflow for grammar and compile-time errors in authored Typst +documents. + +#### Scenario: Compile failure is treated as invalid grammar or document state +- **WHEN** Codex validates a `.typ` file with the skill's required workflow +- **THEN** a non-zero `typst compile` result MUST be treated as a blocking + validation failure + +### Requirement: Text output can be validated through HTML export +The portable authoring skill SHALL define a text-validation workflow based on +Typst HTML output so Codex can inspect textual structure and rendered text +separately from compile success. + +#### Scenario: HTML validation of authored text +- **WHEN** Codex needs to verify rendered text or document structure after a + successful compile +- **THEN** the skill MUST direct Codex to use `typst compile --features html` + to produce HTML output for inspection + +### Requirement: Visual output can be validated through SVG and Playwright +The portable authoring skill SHALL define a visual-validation workflow based on +Typst SVG output and Playwright MCP inspection so Codex can review rendered +layout after compilation. + +#### Scenario: SVG and Playwright visual inspection +- **WHEN** Codex needs to inspect rendered layout or visual regressions in a + user Typst document +- **THEN** the skill MUST direct Codex to compile the document to SVG +- **AND** the skill MUST direct Codex to use Playwright MCP as the visual + inspection step diff --git a/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/tasks.md b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/tasks.md new file mode 100644 index 0000000..a31100d --- /dev/null +++ b/openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/tasks.md @@ -0,0 +1,23 @@ +## 1. Split the skill topology + +- [x] 1.1 Create a repo-local `update-typst-grammar-authoring` skill target under `.codex/skills/` +- [x] 1.2 Move generator scripts, traceability artifacts, and updater-only metadata out of `typst-grammar-authoring` and into `update-typst-grammar-authoring` +- [x] 1.3 Reduce `.codex/skills/typst-grammar-authoring/` to a distributable single-file skill that contains only `SKILL.md` + +## 2. Rewrite the portable authoring skill + +- [x] 2.1 Rewrite `typst-grammar-authoring/SKILL.md` to remove Windows-specific command paths and use platform-neutral path examples +- [x] 2.2 Keep grammar lookup and validation guidance self-contained inside the distributed `typst-grammar-authoring/SKILL.md` +- [x] 2.3 Remove repo-local maintenance instructions, source-path dependencies, and sidecar-file assumptions from the distributed `typst-grammar-authoring/SKILL.md` + +## 3. Build the updater skill workflow + +- [x] 3.1 Add updater instructions for regenerating `typst-grammar-authoring/SKILL.md` from `src/tutorial/reference-grammar.typ` +- [x] 3.2 Keep exact source traceability available as updater-owned artifacts without expanding the portable skill context +- [x] 3.3 Add updater validation guidance for refreshing, reviewing, and diffing the distributed skill output + +## 4. Verify the split + +- [x] 4.1 Verify `.codex/skills/typst-grammar-authoring/` contains only `SKILL.md` +- [x] 4.2 Verify the distributed `SKILL.md` contains no Windows-specific or absolute repo-local paths in command examples +- [x] 4.3 Smoke-test the updater workflow and the portable skill after the split diff --git a/openspec/config.yaml b/openspec/config.yaml index 392946c..1bb2226 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -1,20 +1,14 @@ schema: spec-driven -# Project context (optional) -# This is shown to AI when creating artifacts. -# Add your tech stack, conventions, style guides, domain knowledge, etc. -# Example: -# context: | -# Tech stack: TypeScript, React, Node.js -# We use conventional commits -# Domain: e-commerce platform +context: | + When a change is archived and syncs or creates canonical specs under + `openspec/specs/`, review each resulting `## Purpose` section before calling + the archive complete. + Replace any autogenerated placeholder like + `TBD - created by archiving change ...` with a concise, domain-specific + purpose statement that describes the capability. -# Per-artifact rules (optional) -# Add custom rules for specific artifacts. -# Example: -# rules: -# proposal: -# - Keep proposals under 500 words -# - Always include a "Non-goals" section -# tasks: -# - Break tasks into chunks of max 2 hours +rules: + specs: + - Every spec in `openspec/specs/*/spec.md` must include a concrete `## Purpose` section. + - Never leave the autogenerated archive placeholder in a canonical spec purpose; replace it before reporting archive success. diff --git a/openspec/specs/typst-document-authoring-skill/spec.md b/openspec/specs/typst-document-authoring-skill/spec.md new file mode 100644 index 0000000..8cfce06 --- /dev/null +++ b/openspec/specs/typst-document-authoring-skill/spec.md @@ -0,0 +1,98 @@ +# typst-document-authoring-skill Specification + +## Purpose +Define the canonical requirements for a portable, English-facing Typst authoring skill and the updater and validation workflows that maintain it. + +This specification defines the `typst-document-authoring-skill` capability, which is concretely implemented by the portable `typst-grammar-authoring` skill (distributed as `typst-grammar-authoring/SKILL.md`) and its repo-local updater workflow `update-typst-grammar-authoring`. + +## Requirements +### Requirement: Portable authoring skill is English-facing and self-contained +The distributed `typst-grammar-authoring` skill SHALL be complete as a +single-file, English-facing `SKILL.md` so it can be copied into another +repository without requiring sibling support files. + +#### Scenario: Single-file portable distribution +- **WHEN** `typst-grammar-authoring` is prepared for downstream use +- **THEN** the skill folder MUST be complete with only `SKILL.md` +- **AND** that `SKILL.md` MUST contain the authoring guidance, grammar lookup, + and validation workflow needed by the consuming repository + +### Requirement: Portable authoring skill avoids Windows-specific path examples +The distributed `typst-grammar-authoring/SKILL.md` SHALL use platform-neutral, +forward-slash, or placeholder filesystem paths in commands and examples. + +#### Scenario: Portable command examples +- **WHEN** `typst-grammar-authoring/SKILL.md` is authored or regenerated +- **THEN** command examples MUST use repo-relative or placeholder paths such as + `path/to/document.typ` +- **AND** the file MUST NOT include Windows absolute paths or Windows-style + backslash-only command paths + +### Requirement: Portable authoring skill embeds compact grammar lookup +The distributed `typst-grammar-authoring/SKILL.md` SHALL embed a compact Typst +grammar lookup derived from `src/tutorial/reference-grammar.typ` so it remains +single-file while still teaching canonical syntax patterns. + +#### Scenario: Grammar lookup without sidecar references +- **WHEN** Codex needs a Typst syntax pattern while using + `typst-grammar-authoring` +- **THEN** the skill MUST provide that lookup inside `SKILL.md` +- **AND** the lookup MUST be derived from canonical examples in + `src/tutorial/reference-grammar.typ` + +### Requirement: Updater skill owns regeneration of the portable skill +The repo-local `update-typst-grammar-authoring` skill SHALL own the workflow +that regenerates the distributed `typst-grammar-authoring/SKILL.md` from +`src/tutorial/reference-grammar.typ`. + +#### Scenario: Repo-local regeneration +- **WHEN** a maintainer updates canonical grammar examples from + `src/tutorial/reference-grammar.typ` +- **THEN** `update-typst-grammar-authoring` MUST provide the update workflow +- **AND** that workflow MUST produce the distributed + `typst-grammar-authoring/SKILL.md` + +### Requirement: Updater skill preserves verbose traceability outside portable context +The updater workflow SHALL preserve exact source traceability in separate +maintenance artifacts without injecting verbose canonical reference and +traceability sections into the distributed `typst-grammar-authoring/SKILL.md`. + +#### Scenario: Maintainer needs exact source mapping +- **WHEN** a maintainer needs exact source mapping for a generated grammar entry +- **THEN** `update-typst-grammar-authoring` MUST provide a separate + traceability artifact +- **AND** the distributed `typst-grammar-authoring/SKILL.md` MUST omit verbose + canonical reference and traceability sections from its active context + +### Requirement: Grammar validation uses Typst compilation +The portable authoring skill SHALL define `typst compile` as the required +validation workflow for grammar and compile-time errors in authored Typst +documents. + +#### Scenario: Compile failure is treated as invalid grammar or document state +- **WHEN** Codex validates a `.typ` file with the skill's required workflow +- **THEN** a non-zero `typst compile` result MUST be treated as a blocking + validation failure + +### Requirement: Text output can be validated through HTML export +The portable authoring skill SHALL define a text-validation workflow based on +Typst HTML output so Codex can inspect textual structure and rendered text +separately from compile success. + +#### Scenario: HTML validation of authored text +- **WHEN** Codex needs to verify rendered text or document structure after a + successful compile +- **THEN** the skill MUST direct Codex to use `typst compile --features html` + to produce HTML output for inspection + +### Requirement: Visual output can be validated through SVG and Playwright +The portable authoring skill SHALL define a visual-validation workflow based on +Typst SVG output and Playwright MCP inspection so Codex can review rendered +layout after compilation. + +#### Scenario: SVG and Playwright visual inspection +- **WHEN** Codex needs to inspect rendered layout or visual regressions in a + user Typst document +- **THEN** the skill MUST direct Codex to compile the document to SVG +- **AND** the skill MUST direct Codex to use Playwright MCP as the visual + inspection step