ITADN

Add front matter support (`CMARK_OPT_FRONT_MATTER`)

#603Pull Requestsamuel-williams-shopify 创建于 2026-04-13
This PR adds opt-in front matter parsing to cmark. When `CMARK_OPT_FRONT_MATTER` is set (or `--front-matter` is passed to the executable), a `---` delimited block at the very start of the document is captured as a `CMARK_NODE_FRONT_MATTER` node and excluded from HTML output. The feature is entirely opt-in; existing behaviour is unchanged when the flag is not set. ## Motivation Many Markdown-based tools (static site generators, documentation systems, notebook formats) attach structured metadata to documents using a front matter block. Without native support, every such tool must pre-process the input before handing it to cmark, losing source-position information and making it impossible to round-trip the document. ## Design ### Node type `CMARK_NODE_FRONT_MATTER` is a first-class block node type, added to the enum after `CMARK_NODE_THEMATIC_BREAK`. It stores data identically to `CMARK_NODE_CODE_BLOCK`: - `cmark_node_get_literal()` — the raw content between the delimiters. - `cmark_node_get_fence_info()` — an optional format hint from the opening delimiter line (e.g. `--- yaml`, `--- toml`). The implementation is format-agnostic. How the content is interpreted (YAML, TOML, JSON, …) is left entirely to the caller. ### Parser integration The feature is implemented as a small state machine in `src/front_matter.c`. Two fields are added to `cmark_parser`: - `front_matter_scanning` — true from the opening `---` until the closing `---` is found or the document ends. - `front_matter_buf` / `front_matter_info` — `cmark_strbuf` accumulators for the content and info string respectively. `cmark_front_matter_process_line()` is called from `S_process_line()` in `blocks.c` immediately after `parser->line_number` is incremented, so line 1 is the trigger. The state lives on the parser struct, so the feature works correctly regardless of how many times `cmark_parser_feed()` is called. ### Delimiter rules - **Opening**: `---` on the very first line, optionally followed by an info string (e.g. `--- yaml`). A fourth consecutive dash (`----`) is **not** treated as a front matter opener — it remains a thematic break. - **Closing**: exactly `---` with optional trailing whitespace. Note that `...` (the YAML document-end marker) is intentionally **not** supported as a closing delimiter; this implementation is format-agnostic and `...` has no meaning outside of YAML. - **No closing delimiter**: if the document ends without a closing `---`, the entire document body (after the opening delimiter) is treated as front matter. ### Renderers All renderers handle `CMARK_NODE_FRONT_MATTER`: | Renderer | Behaviour | |-------------|-----------| | HTML | Silent (front matter is metadata, not content) | | Plaintext | Silent | | CommonMark | Round-trips with delimiters and info string | | LaTeX | Silent | | Man | Silent | | XML | Emitted as a `front_matter` element with `xml:space="preserve"` and an optional `info` attribute | ## Files changed - `src/front_matter.c` — new: state machine implementation - `src/front_matter.h` — new: public declaration - `src/cmark.h` — `CMARK_NODE_FRONT_MATTER` enum entry, `CMARK_OPT_FRONT_MATTER` flag - `src/parser.h` — `front_matter_scanning`, `front_matter_buf`, `front_matter_info` fields - `src/blocks.c` — strbuf lifecycle, `S_process_line` hook, `cmark_parser_finish` hook - `src/node.c` — `S_free_nodes`, `get_type_string`, `get_literal`, `set_literal`, `get_fence_info`, `set_fence_info` - `src/main.c` — `--front-matter` flag - `src/html.c`, `src/commonmark.c`, `src/latex.c`, `src/man.c`, `src/xml.c` — renderer cases - `src/CMakeLists.txt` — build - `test/front_matter.txt` — new: spec-format test fixture (10 examples) - `test/CMakeLists.txt` — test wiring - `api_test/main.c` — `test_front_matter()`: 12 assertions covering node type, literal content, info string, source position, no-flag behaviour, no closing delimiter, and multi-feed correctness - `changelog.txt` ## Compatibility `CMARK_OPT_FRONT_MATTER` uses bit 11 (`1 << 11`). Note that the cmark-gfm fork uses this bit for `CMARK_OPT_GITHUB_PRE_LANG`; these are separate codebases with independent option namespaces. `CMARK_NODE_LAST_BLOCK` is updated from `CMARK_NODE_THEMATIC_BREAK` to `CMARK_NODE_FRONT_MATTER`. Code that iterates over block node types using this sentinel will automatically include the new type. ## Testing ``` cmake -S . -B build -DBUILD_SHARED_LIBS=ON cmake --build build ctest --test-dir build # 10/10 tests pass, including api_test and front_matter_executable ```
合并状态:未合并 1 条评论