krz/orgo

Lightning fast org-mode static site generator. fast go org-mode static-site-generator

Commit f274b51656

f274b516569365d352ed75206f6678f862cfd7f7

parent: ce3f2623d5

Unregistered key

cmc <hello@cleberg.net> · 2026-08-23 05:05 UTC
committer: <noreply@github.com>

Point scope references at the org-support guide (#29)

Ten comments cited `README §"v1 scope"` or `README §OUT`. README.md has no
such section — that content now lives in `docs/guide/05-org-support.org`, which
README links to from the docs table. The citations now name that file, and the
"v1" phrasing goes with them, since nothing defines a v1 scope line any more.

Two claims in `parser.rs`'s header were stale as of #27 and #28: unknown block
types do not "keep their content verbatim as example blocks" — they render as
special blocks holding parsed org — and `#+TBLFM:` is no longer described as
out of scope.

Adding the section back to README instead would put the supported/unsupported
list in two places, which is how these drifted apart to begin with.

Layout: unified · split

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