Commit f274b51656
Unregistered key
Layout: unified · split
fixtures/outofscope.org +3 −2
| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | #+TITLE: Out of Scope |
| 2 | 2 | #+INCLUDE: "other.org" |
| 3 | 3 | |
| 4 | Every construct here is on the README's explicit OUT list. The contract is not that we | |
| 5 | handle them — it is that they degrade predictably and never crash the build. | |
| 4 | Every construct here is on the explicit "Not supported" list in the org-support guide. | |
| 5 | The contract is not that we handle them — it is that they degrade predictably and never | |
| 6 | crash the build. | |
| 6 | 7 | |
| 7 | 8 | * Babel |
| 8 | 9 | |
src/audit.rs +8 −7
| @@ -1,9 +1,9 @@ | ||
| 1 | 1 | //! Corpus audit (spec §5, Phase 0): measure which org constructs a real corpus actually |
| 2 | //! uses, and classify each against the v1 IN/OUT line. | |
| 2 | //! uses, and classify each against the supported/unsupported line. | |
| 3 | 3 | //! |
| 4 | //! This exists because the v1 scope was, on the README's own admission, *recommended* | |
| 5 | //! rather than measured — a guess about which slice of org matters. A guess about a | |
| 6 | //! corpus is a hypothesis, and this is the experiment. It answers two questions: | |
| 4 | //! This exists because the scope was recommended rather than measured — a guess about | |
| 5 | //! which slice of org matters. A guess about a corpus is a hypothesis, and this is the | |
| 6 | //! experiment. It answers two questions: | |
| 7 | 7 | //! |
| 8 | 8 | //! 1. **Coverage** — of the constructs this corpus uses, which do we handle? A construct |
| 9 | 9 | //! that is common here and out of scope is a scope bug, not a corpus quirk. |
| @@ -24,12 +24,13 @@ use anyhow::{Context, Result}; | ||
| 24 | 24 | use camino::{Utf8Path, Utf8PathBuf}; |
| 25 | 25 | use walkdir::WalkDir; |
| 26 | 26 | |
| 27 | /// Where a construct sits relative to the v1 scope line (README §"v1 scope"). | |
| 27 | /// Where a construct sits relative to the supported set, which | |
| 28 | /// `docs/guide/05-org-support.org` defines. | |
| 28 | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
| 29 | 30 | pub enum Scope { |
| 30 | /// v1 handles this. | |
| 31 | /// orgo handles this. | |
| 31 | 32 | In, |
| 32 | /// v1 deliberately excludes this; it degrades predictably. | |
| 33 | /// orgo deliberately excludes this; it degrades predictably. | |
| 33 | 34 | Out, |
| 34 | 35 | } |
| 35 | 36 | |
src/main.rs +2 −2
| @@ -90,8 +90,8 @@ enum Command { | ||
| 90 | 90 | /// Output directory to remove. |
| 91 | 91 | output: Utf8PathBuf, |
| 92 | 92 | }, |
| 93 | /// Audit a corpus: report which org constructs it uses and how they land against | |
| 94 | /// the v1 scope line. Reports names, counts and locations — never document text. | |
| 93 | /// Audit a corpus: report which org constructs it uses and how each one lands. | |
| 94 | /// Reports names, counts and locations — never document text. | |
| 95 | 95 | Audit { |
| 96 | 96 | /// Source directory (or single `.org` file) to audit. |
| 97 | 97 | input: Utf8PathBuf, |
src/parser.rs +16 −13
| @@ -9,16 +9,19 @@ | ||
| 9 | 9 | //! PARSE is a pure function of a single file's bytes (spec §2.1): it never depends on |
| 10 | 10 | //! another file, which is what makes content-hash caching sound. |
| 11 | 11 | //! |
| 12 | //! Scope is the v1 IN list (README §"v1 scope"): headings with nesting, TODO keywords, | |
| 13 | //! priorities, tags and property drawers; paragraphs; plain lists (unordered, ordered, | |
| 14 | //! description) with checkboxes and nesting; tables; source/example/quote/center/export | |
| 15 | //! blocks; footnotes; `#+` keywords; inline markup, links, timestamps; images with | |
| 16 | //! `#+CAPTION`/`#+ATTR_HTML`. | |
| 12 | //! Scope is `docs/guide/05-org-support.org` §Supported: headings with nesting, TODO | |
| 13 | //! keywords, priorities, tags and property drawers; paragraphs; plain lists (unordered, | |
| 14 | //! ordered, description) with checkboxes and nesting; tables; footnotes; `#+` keywords; | |
| 15 | //! source, example, quote, center and export blocks; inline markup, links, timestamps; | |
| 16 | //! images with `#+CAPTION`/`#+ATTR_HTML`. | |
| 17 | 17 | //! |
| 18 | //! Out-of-scope constructs are parsed-and-ignored, never fatal: babel `:results` and | |
| 19 | //! `#+TBLFM:` are inert keywords, unknown block types keep their content verbatim as | |
| 20 | //! example blocks, generic drawers are captured and dropped at render, and LaTeX, | |
| 21 | //! macros and radio targets survive as literal text. | |
| 18 | //! A block name with no dedicated handling is a special block: a div carrying the name, | |
| 19 | //! holding parsed org, which is what org's exporter emits for it. | |
| 20 | //! | |
| 21 | //! Out-of-scope constructs are parsed-and-ignored, never fatal: babel `:results` is an | |
| 22 | //! inert keyword, generic drawers are captured and dropped at render, and LaTeX, macros | |
| 23 | //! and radio targets survive as literal text. `#+TBLFM:` is inert for the same reason | |
| 24 | //! org's exporter leaves it alone: it does not recalculate on export either. | |
| 22 | 25 | |
| 23 | 26 | use camino::Utf8Path; |
| 24 | 27 | use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; |
| @@ -408,7 +411,7 @@ fn parse_elements(lines: &[&str], base: usize, diags: &mut Vec<Diagnostic>) -> V | ||
| 408 | 411 | } |
| 409 | 412 | if let Some((key, value)) = keyword_kv(line) { |
| 410 | 413 | if key.eq_ignore_ascii_case("INCLUDE") { |
| 411 | // Never expanded (README §OUT). Expanding it means resolving paths, | |
| 414 | // Never expanded (§"Not supported"). Expanding it means resolving paths, | |
| 412 | 415 | // recursion and `:lines`/`:only-contents`; dropping it silently means a |
| 413 | 416 | // page missing content nobody was told about. Saying so is the honest |
| 414 | 417 | // middle, and `--strict` turns it into a failure. |
| @@ -422,7 +425,7 @@ fn parse_elements(lines: &[&str], base: usize, diags: &mut Vec<Diagnostic>) -> V | ||
| 422 | 425 | }); |
| 423 | 426 | } |
| 424 | 427 | if key.eq_ignore_ascii_case("RESULTS") { |
| 425 | // Babel is never executed (README §OUT), so a checked-in `#+RESULTS:` | |
| 428 | // Babel is never executed (§"Not supported"), so a checked-in `#+RESULTS:` | |
| 426 | 429 | // block is output from someone else's Emacs session at some other time. |
| 427 | 430 | // Emitting it would put unverifiable content on the page dressed as |
| 428 | 431 | // real content, so the block it labels is dropped. |
| @@ -617,7 +620,7 @@ fn unescape_block_line(line: &str) -> String { | ||
| 617 | 620 | |
| 618 | 621 | /// `:NAME:` … `:END:` at block level. A PROPERTIES drawer directly under a heading is |
| 619 | 622 | /// consumed by [`parse_section_body`]; anything reaching here is a generic drawer, |
| 620 | /// which the renderer drops (README §OUT). | |
| 623 | /// which the renderer drops (§"Not supported"). | |
| 621 | 624 | fn parse_drawer( |
| 622 | 625 | lines: &[&str], |
| 623 | 626 | start: usize, |
| @@ -1353,7 +1356,7 @@ fn try_timestamp(chars: &[char], i: usize) -> Option<(Object, usize)> { | ||
| 1353 | 1356 | |
| 1354 | 1357 | /// One bracketed stamp → `(start, same-day end, has_time, index past the bracket)`. |
| 1355 | 1358 | /// Day names (`Mon`) and repeater/warning cookies (`+1w`, `-2d`) are recognized and |
| 1356 | /// discarded — they carry no export meaning (README §OUT: agenda semantics). | |
| 1359 | /// discarded — they carry no export meaning (§"Not supported": agenda semantics). | |
| 1357 | 1360 | fn parse_stamp( |
| 1358 | 1361 | chars: &[char], |
| 1359 | 1362 | i: usize, |
src/render.rs +8 −7
| @@ -8,12 +8,13 @@ | ||
| 8 | 8 | //! and its cost must be cache-skippable (spec §4.2). Emit CSS classes, not inline |
| 9 | 9 | //! styles, so themes live in the stylesheet (spec §3.2). |
| 10 | 10 | //! |
| 11 | //! Renders the v1 IN set: headings (always anchored, with TODO keyword, priority and | |
| 12 | //! tags), paragraphs, plain lists (unordered/ordered/description, nested, with | |
| 13 | //! checkboxes), tables, source blocks (syntect-highlighted), example/quote/center | |
| 14 | //! blocks, HTML export blocks, horizontal rules, images and captioned figures, | |
| 15 | //! footnotes, timestamps, and inline markup. Out-of-scope elements (generic drawers, | |
| 16 | //! comments, stray keywords, non-HTML export blocks) render to nothing. | |
| 11 | //! Renders the supported set (`docs/guide/05-org-support.org`): headings (always | |
| 12 | //! anchored, with TODO keyword, priority and tags), paragraphs, plain lists | |
| 13 | //! (unordered/ordered/description, nested, with checkboxes), tables, source blocks | |
| 14 | //! (syntect-highlighted), example/quote/center blocks, HTML export blocks, horizontal | |
| 15 | //! rules, images and captioned figures, footnotes, timestamps, and inline markup. | |
| 16 | //! Out-of-scope elements (generic drawers, comments, stray keywords, non-HTML export | |
| 17 | //! blocks) render to nothing. | |
| 17 | 18 | |
| 18 | 19 | use std::collections::HashMap; |
| 19 | 20 | use std::sync::OnceLock; |
| @@ -463,7 +464,7 @@ impl Renderer<'_> { | ||
| 463 | 464 | out.push_str("</div>\n"); |
| 464 | 465 | } |
| 465 | 466 | // An `html` export block is verbatim output by definition; every other |
| 466 | // backend is out of scope and drops (README §OUT). | |
| 467 | // backend is out of scope and drops (§"Not supported"). | |
| 467 | 468 | Element::ExportBlock { backend, raw } => { |
| 468 | 469 | if backend.eq_ignore_ascii_case("html") { |
| 469 | 470 | out.push_str(raw); |
tests/constructs.rs +6 −6
| @@ -1,10 +1,10 @@ | ||
| 1 | //! Golden-file coverage of the v1 scope line (README §"v1 scope"). | |
| 1 | //! Golden-file coverage of the scope line `docs/guide/05-org-support.org` draws. | |
| 2 | 2 | //! |
| 3 | 3 | //! Two halves, and the second is the point: |
| 4 | 4 | //! |
| 5 | //! - **IN** — every construct the v1 scope claims gets an element-tree snapshot (parser | |
| 5 | //! - **IN** — every construct the guide claims gets an element-tree snapshot (parser | |
| 6 | 6 | //! correctness) and a rendered-HTML snapshot (renderer correctness). |
| 7 | //! - **OUT** — every construct the v1 scope explicitly excludes gets an assertion that it | |
| 7 | //! - **OUT** — every construct the guide explicitly excludes gets an assertion that it | |
| 8 | 8 | //! *degrades predictably*: parsed and ignored, content preserved where that is the |
| 9 | 9 | //! honest fallback, never a crash and never a half-rendered artifact. |
| 10 | 10 | //! |
| @@ -34,7 +34,7 @@ fn render_fixture(name: &str) -> String { | ||
| 34 | 34 | } |
| 35 | 35 | |
| 36 | 36 | // --------------------------------------------------------------------------- |
| 37 | // IN: the constructs v1 promises to handle | |
| 37 | // IN: the constructs the guide promises to handle | |
| 38 | 38 | // --------------------------------------------------------------------------- |
| 39 | 39 | |
| 40 | 40 | #[test] |
| @@ -180,7 +180,7 @@ fn unknown_source_language_falls_back_to_plain_code() { | ||
| 180 | 180 | } |
| 181 | 181 | |
| 182 | 182 | // --------------------------------------------------------------------------- |
| 183 | // OUT: the constructs v1 explicitly excludes must degrade, not explode | |
| 183 | // OUT: the constructs the guide explicitly excludes must degrade, not explode | |
| 184 | 184 | // --------------------------------------------------------------------------- |
| 185 | 185 | |
| 186 | 186 | /// The whole OUT fixture parses and renders. This is the crash gate. |
| @@ -222,7 +222,7 @@ fn table_formulas_are_inert() { | ||
| 222 | 222 | ); |
| 223 | 223 | } |
| 224 | 224 | |
| 225 | /// LaTeX, macros and radio targets have no v1 semantics, so they survive as the literal | |
| 225 | /// LaTeX, macros and radio targets have no orgo semantics, so they survive as the literal | |
| 226 | 226 | /// text the author typed — lossless, and obviously unhandled to a reader. |
| 227 | 227 | #[test] |
| 228 | 228 | fn latex_macros_and_radio_targets_stay_literal() { |
tests/snapshots/constructs__out_of_scope_html.snap +1 −1
| @@ -2,7 +2,7 @@ | ||
| 2 | 2 | source: tests/constructs.rs |
| 3 | 3 | expression: "render_fixture(\"outofscope.org\")" |
| 4 | 4 | --- |
| 5 | <p>Every construct here is on the README's explicit OUT list. The contract is not that we handle them — it is that they degrade predictably and never crash the build.</p> | |
| 5 | <p>Every construct here is on the explicit "Not supported" list in the org-support guide. The contract is not that we handle them — it is that they degrade predictably and never crash the build.</p> | |
| 6 | 6 | <h2 id="babel">Babel</h2> |
| 7 | 7 | <pre><code class="language-sh highlight"><span class="source shell bash"><span class="meta function-call shell"><span class="support function echo shell">echo</span></span><span class="meta function-call arguments shell"> <span class="string quoted double shell"><span class="punctuation definition string begin shell">"</span>the block renders; :results is never executed<span class="punctuation definition string end shell">"</span></span></span></span></code></pre> |
| 8 | 8 | <h2 id="table-formulas">Table formulas</h2> |