| @@ -71,9 +71,9 @@ pub struct Audit { |
| 71 | pub link_schemes: BTreeMap<String, Tally>, |
71 | pub link_schemes: BTreeMap<String, Tally>, |
| 72 | } |
72 | } |
| 73 | |
73 | |
| 74 | /// Names the implementation understands, so the census can flag everything else. These |
74 | /// Names the implementation reads by name, so the census can mark everything else. These |
| 75 | /// are the *recognized* sets, not the supported ones: `INCLUDE` is recognized (it is |
75 | /// are the *recognized* sets, not the supported ones: `INCLUDE` is recognized and |
| 76 | /// deliberately inert) while an unlisted keyword is a genuine blind spot. |
76 | /// deliberately inert. |
| 77 | const KNOWN_KEYWORDS: &[&str] = &[ |
77 | const KNOWN_KEYWORDS: &[&str] = &[ |
| 78 | "TITLE", "AUTHOR", "DATE", "EMAIL", "LANGUAGE", "OPTIONS", "FILETAGS", "DESCRIPTION", |
78 | "TITLE", "AUTHOR", "DATE", "EMAIL", "LANGUAGE", "OPTIONS", "FILETAGS", "DESCRIPTION", |
| 79 | "KEYWORDS", "CAPTION", "NAME", "ATTR_HTML", "RESULTS", "TBLFM", "INCLUDE", "TODO", |
79 | "KEYWORDS", "CAPTION", "NAME", "ATTR_HTML", "RESULTS", "TBLFM", "INCLUDE", "TODO", |
| @@ -97,20 +97,36 @@ const KNOWN_SCHEMES: &[&str] = &[ |
| 97 | "relative", |
97 | "relative", |
| 98 | ]; |
98 | ]; |
| 99 | |
99 | |
| |
100 | /// The census markers. A name with dedicated handling carries none; `PASS_THROUGH` says |
| |
101 | /// nothing reads the name but it works anyway; `UNKNOWN` is the blind-spot signal. |
| |
102 | const HANDLED: &str = ""; |
| |
103 | const PASS_THROUGH: &str = "\u{2014}"; |
| |
104 | const UNKNOWN: &str = "???"; |
| |
105 | |
| 100 | impl Audit { |
106 | impl Audit { |
| 101 | /// Is this name one the implementation recognizes? |
107 | /// How the report marks a census name: the recognized set to look the name up in, |
| 102 | pub fn is_known(kind: Census, name: &str) -> bool { |
108 | /// and what an unlisted name means for that census. |
| 103 | let known = match kind { |
109 | pub fn marker(kind: Census, name: &str) -> &'static str { |
| 104 | Census::Keyword => KNOWN_KEYWORDS, |
110 | let (known, unlisted) = match kind { |
| |
111 | // An unlisted keyword is not a blind spot. Reaching templates as |
| |
112 | // `page.keywords.<name>` is the designed behaviour, so every keyword name |
| |
113 | // works; what the census reports is which ones have dedicated handling. |
| |
114 | // `LEDE`, read by the shipped docs theme's template, was the case that made |
| |
115 | // this concrete: `???` on it was a claim about orgo's source, not a gap. |
| |
116 | Census::Keyword => (KNOWN_KEYWORDS, PASS_THROUGH), |
| 105 | // Every block name renders, and renders as org renders it: the names in |
117 | // Every block name renders, and renders as org renders it: the names in |
| 106 | // `block_construct` through dedicated handling, every other name as a special |
118 | // `block_construct` through dedicated handling, every other name as a special |
| 107 | // block — a div carrying the name, holding parsed org, which is exactly what |
119 | // block — a div carrying the name, holding parsed org, which is exactly what |
| 108 | // org's exporter emits. No block name is a blind spot. |
120 | // org's exporter emits. No block name is a blind spot. |
| 109 | Census::Block => return true, |
121 | Census::Block => return HANDLED, |
| 110 | Census::Drawer => KNOWN_DRAWERS, |
122 | Census::Drawer => (KNOWN_DRAWERS, UNKNOWN), |
| 111 | Census::Scheme => KNOWN_SCHEMES, |
123 | Census::Scheme => (KNOWN_SCHEMES, UNKNOWN), |
| 112 | }; |
124 | }; |
| 113 | known.iter().any(|k| k.eq_ignore_ascii_case(name)) |
125 | if known.iter().any(|k| k.eq_ignore_ascii_case(name)) { |
| |
126 | HANDLED |
| |
127 | } else { |
| |
128 | unlisted |
| |
129 | } |
| 114 | } |
130 | } |
| 115 | } |
131 | } |
| 116 | |
132 | |
| @@ -330,21 +346,27 @@ impl Audit { |
| 330 | rest = &after[end..]; |
346 | rest = &after[end..]; |
| 331 | } |
347 | } |
| 332 | |
348 | |
| 333 | if has_timestamp(line) { |
349 | // The rest read a line that has had its `=verbatim=` and `~code~` spans blanked: |
| |
350 | // a construct shown inside verbatim is displayed rather than rendered, so a page |
| |
351 | // documenting org syntax is not a use of the syntax it names. Emphasis pairs |
| |
352 | // below deliberately still see the raw line — `=` is one of the markers scanned. |
| |
353 | let bare = without_literal_spans(line); |
| |
354 | |
| |
355 | if has_timestamp(&bare) { |
| 334 | self.count(Scope::In, "timestamp", at); |
356 | self.count(Scope::In, "timestamp", at); |
| 335 | } |
357 | } |
| 336 | if line.contains("{{{") { |
358 | if bare.contains("{{{") { |
| 337 | self.count(Scope::Out, "macro call", at); |
359 | self.count(Scope::Out, "macro call", at); |
| 338 | } |
360 | } |
| 339 | if line.contains("<<<") { |
361 | if bare.contains("<<<") { |
| 340 | self.count(Scope::Out, "radio target", at); |
362 | self.count(Scope::Out, "radio target", at); |
| 341 | } else if line.contains("<<") && line.contains(">>") { |
363 | } else if bare.contains("<<") && bare.contains(">>") { |
| 342 | self.count(Scope::Out, "internal target", at); |
364 | self.count(Scope::Out, "internal target", at); |
| 343 | } |
365 | } |
| 344 | if line.contains("\\begin{") || latex_inline(line) { |
366 | if bare.contains("\\begin{") || latex_inline(&bare) { |
| 345 | self.count(Scope::Out, "LaTeX fragment", at); |
367 | self.count(Scope::Out, "LaTeX fragment", at); |
| 346 | } |
368 | } |
| 347 | if entity_ref(line) { |
369 | if entity_ref(&bare) { |
| 348 | // Rendered, and rendered as org renders it: `\alpha` becomes `α` in |
370 | // Rendered, and rendered as org renders it: `\alpha` becomes `α` in |
| 349 | // both exporters. `fixtures/audit-entities.org` holds the oracle to that. |
371 | // both exporters. `fixtures/audit-entities.org` holds the oracle to that. |
| 350 | self.count(Scope::In, "entity (\\name)", at); |
372 | self.count(Scope::In, "entity (\\name)", at); |
| @@ -510,11 +532,7 @@ fn latex_inline(line: &str) -> bool { |
| 510 | /// Only names org knows count, matching [`crate::parser`]'s rule for rendering one: a |
532 | /// Only names org knows count, matching [`crate::parser`]'s rule for rendering one: a |
| 511 | /// Windows path (`C:\Users\me`) and a namespaced identifier (`Tumblr\API\Client`) are not |
533 | /// Windows path (`C:\Users\me`) and a namespaced identifier (`Tumblr\API\Client`) are not |
| 512 | /// entity references. |
534 | /// entity references. |
| 513 | /// |
| |
| 514 | /// Verbatim and code spans are skipped: `=\alpha=` shows the name rather than rendering |
| |
| 515 | /// the character, so it is not a use of the feature. |
| |
| 516 | fn entity_ref(line: &str) -> bool { |
535 | fn entity_ref(line: &str) -> bool { |
| 517 | let line = without_literal_spans(line); |
| |
| 518 | let chars: Vec<char> = line.chars().collect(); |
536 | let chars: Vec<char> = line.chars().collect(); |
| 519 | for (i, c) in chars.iter().enumerate() { |
537 | for (i, c) in chars.iter().enumerate() { |
| 520 | if *c != '\\' { |
538 | if *c != '\\' { |
| @@ -642,13 +660,9 @@ pub fn report(audit: &Audit) -> String { |
| 642 | names.sort_by(|a, b| b.1.occurrences.cmp(&a.1.occurrences).then(a.0.cmp(b.0))); |
660 | names.sort_by(|a, b| b.1.occurrences.cmp(&a.1.occurrences).then(a.0.cmp(b.0))); |
| 643 | out.push_str(&format!("\n{title}\n")); |
661 | out.push_str(&format!("\n{title}\n")); |
| 644 | for (name, tally) in names { |
662 | for (name, tally) in names { |
| 645 | let flag = if Audit::is_known(kind, name) { |
| |
| 646 | " " |
| |
| 647 | } else { |
| |
| 648 | "??? " |
| |
| 649 | }; |
| |
| 650 | out.push_str(&format!( |
663 | out.push_str(&format!( |
| 651 | "{flag}{:<32} {:>8} {:>7} {}\n", |
664 | "{:<4}{:<32} {:>8} {:>7} {}\n", |
| |
665 | Audit::marker(kind, name), |
| 652 | name, |
666 | name, |
| 653 | tally.occurrences, |
667 | tally.occurrences, |
| 654 | tally.files, |
668 | tally.files, |
| @@ -656,6 +670,10 @@ pub fn report(audit: &Audit) -> String { |
| 656 | )); |
670 | )); |
| 657 | } |
671 | } |
| 658 | } |
672 | } |
| 659 | out.push_str("\n`???` marks a name the implementation does not recognize at all.\n"); |
673 | out.push_str( |
| |
674 | "\n`???` marks a name the implementation does not recognize at all.\n\ |
| |
675 | `\u{2014}` marks a keyword with no dedicated handling; it reaches templates \ |
| |
676 | as `page.keywords.<name>`.\n", |
| |
677 | ); |
| 660 | out |
678 | out |
| 661 | } |
679 | } |