krz/orgo

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

Commit ce3f2623d5

ce3f2623d5c9eede9c2179aa21afcdf1a4fea05e

parent: 809ac9d720

Unregistered key

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

audit: special blocks are supported, not blind spots (#28)

`#+begin_note` was reported twice as a gap: `??? NOTE` in the block census and
`OUT unmodelled block type` in the construct table. Neither is true. Any block
name with no dedicated handling renders as a div carrying that name and holding
parsed org, which is exactly what org's HTML exporter emits for it — verified
against Emacs 30.2, and already covered by the oracle, since
`fixtures/blocks.org` carries a `#+BEGIN_NOTE` and reports 0 unexplained
divergences. `docs/guide/05-org-support.org:96` has documented this all along.

So no block name is out of scope and none is unrecognised. `scope_of_block` and
`KNOWN_BLOCKS` are gone with it.

`VERSE` and `COMMENT` were also mislabelled: both are handled (a comment block
is dropped from the page, as in org) but fell through to "unmodelled block
type". They now have their own names.

cleberg.net's corpus audits at 100.0% coverage, 0 out-of-scope.

Closes #25

Layout: unified · split

docs/guide/09-auditing.org +5 −9
@@ -26,26 +26,22 @@ IN heading 1148 176 blog/2018-11-28-aes-encr
2626IN verbatim 1048 130 blog/2018-11-28-aes-encryption.org:79
2727...
2828IN table formula (#+TBLFM:) 4 1 blog/2024-08-11-org-mode-features.org:191
29OUT unmodelled block type 1 1 blog/2026-03-03-auditing-aws-s3.org:50
29IN special block 1 1 blog/2026-03-03-auditing-aws-s3.org:50
3030
31coverage: 9001 in-scope use(s) (100.0%), 1 out-of-scope (0.0%)
31coverage: 9002 in-scope use(s) (100.0%), 0 out-of-scope (0.0%)
3232
3333KEYWORDS
3434 SLUG 180 180 blog/2018-11-28-aes-encryption.org:4
3535 TITLE 180 180 blog/2018-11-28-aes-encryption.org:2
3636...
37
38BLOCK TYPES
39 SRC 941 123 blog/2018-11-28-cpp-compiler.org:17
40 QUOTE 115 48 blog/2018-11-28-aes-encryption.org:21
41??? NOTE 1 1 blog/2026-03-03-auditing-aws-s3.org:50
4237#+END_EXAMPLE
4338
4439- =IN= is supported; =OUT= is excluded by design and degrades as described in
4540 [[file:05-org-support.org][Org support]].
4641- The *coverage* line is the number to look at first.
47- =???= marks a name orgo does not recognise at all — here =#+begin_note=, which one
48 post uses once. That is the blind-spot signal: not "known unsupported", but unknown.
42- =???= marks a name orgo does not recognise at all. That is the blind-spot signal —
43 not "known unsupported", but unknown — and this corpus has none. Block names never
44 carry it: an unrecognised one is still a special block, and still renders.
4945
5046Four censuses follow the construct table: every distinct =#+KEYWORD:=, block type,
5147drawer name and link scheme in the corpus. A =???= in any of them is worth a look.
src/audit.rs +9 −17
@@ -83,12 +83,6 @@ const KNOWN_KEYWORDS: &[&str] = &[
8383 // Leaving them out reported the corpus's most-used keyword as unrecognized.
8484 "SLUG", "DRAFT", "TEMPLATE",
8585];
86/// Blocks with dedicated handling. Any *other* name renders as a special block — a div
87/// with that name holding parsed org — so an unlisted block is a note about what a corpus
88/// contains rather than a construct that will be lost.
89const KNOWN_BLOCKS: &[&str] = &[
90 "SRC", "QUOTE", "EXAMPLE", "CENTER", "EXPORT", "VERSE", "COMMENT",
91];
9286const KNOWN_DRAWERS: &[&str] = &["PROPERTIES", "LOGBOOK", "END"];
9387/// Keyword names conventional enough to be worth flagging when they lead a heading.
9488/// A custom sequence is only *real* if some `#+TODO:` declares it, which the census
@@ -107,7 +101,11 @@ impl Audit {
107101 pub fn is_known(kind: Census, name: &str) -> bool {
108102 let known = match kind {
109103 Census::Keyword => KNOWN_KEYWORDS,
110 Census::Block => KNOWN_BLOCKS,
104 // Every block name renders, and renders as org renders it: the names in
105 // `block_construct` through dedicated handling, every other name as a special
106 // block — a div carrying the name, holding parsed org, which is exactly what
107 // org's exporter emits. No block name is a blind spot.
108 Census::Block => return true,
111109 Census::Drawer => KNOWN_DRAWERS,
112110 Census::Scheme => KNOWN_SCHEMES,
113111 };
@@ -198,7 +196,7 @@ impl Audit {
198196 if let Some(rest) = trimmed.to_ascii_uppercase().strip_prefix("#+BEGIN_") {
199197 let kind = rest.split_whitespace().next().unwrap_or("").to_string();
200198 self.count_census(Census::Block, &kind, &at);
201 self.count(scope_of_block(&kind), block_construct(&kind), &at);
199 self.count(Scope::In, block_construct(&kind), &at);
202200 if trimmed.to_ascii_uppercase().contains(":RESULTS") {
203201 self.count(Scope::Out, "babel header args (:results)", &at);
204202 }
@@ -440,14 +438,6 @@ fn is_drawer(trimmed: &str) -> bool {
440438 && t.len() > 2
441439}
442440
443fn scope_of_block(kind: &str) -> Scope {
444 if KNOWN_BLOCKS.iter().any(|k| k.eq_ignore_ascii_case(kind)) {
445 Scope::In
446 } else {
447 Scope::Out
448 }
449}
450
451441fn block_construct(kind: &str) -> &'static str {
452442 match kind.to_ascii_uppercase().as_str() {
453443 "SRC" => "source block",
@@ -455,7 +445,9 @@ fn block_construct(kind: &str) -> &'static str {
455445 "EXAMPLE" => "example block",
456446 "CENTER" => "center block",
457447 "EXPORT" => "export block",
458 _ => "unmodelled block type",
448 "VERSE" => "verse block",
449 "COMMENT" => "comment block",
450 _ => "special block",
459451 }
460452}
461453
tests/constructs.rs +21
@@ -816,3 +816,24 @@ fn audit_counts_table_formulas_as_in_scope() {
816816 "a #+TBLFM: table orgo renders exactly as Emacs does must not read as a gap:\n{line}"
817817 );
818818}
819
820/// A block name with no dedicated handling is a special block, which org itself renders
821/// as a div carrying the name — `fixtures/blocks.org` holds `#+BEGIN_NOTE` and is in the
822/// Emacs oracle. So it is neither out of scope nor a `???` blind spot.
823#[test]
824fn audit_counts_special_blocks_as_in_scope() {
825 let report = audit_fixture("blocks.org");
826 let line = report
827 .lines()
828 .find(|l| l.contains("special block"))
829 .expect("the NOTE block is counted as a special block");
830 assert!(
831 line.starts_with("IN "),
832 "a block orgo renders exactly as Emacs does must not read as a gap:\n{line}"
833 );
834 let flagged: Vec<&str> = report.lines().filter(|l| l.starts_with("???")).collect();
835 assert!(
836 flagged.is_empty(),
837 "no block name is unrecognised — every one renders: {flagged:?}"
838 );
839}