| @@ -71,9 +71,9 @@ pub struct Audit { |
| 71 | 71 | pub link_schemes: BTreeMap<String, Tally>, |
| 72 | 72 | } |
| 73 | 73 | |
| 74 | | /// Names the implementation understands, so the census can flag everything else. These |
| 75 | | /// are the *recognized* sets, not the supported ones: `INCLUDE` is recognized (it is |
| 76 | | /// deliberately inert) while an unlisted keyword is a genuine blind spot. |
| 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 and |
| 76 | /// deliberately inert. |
| 77 | 77 | const KNOWN_KEYWORDS: &[&str] = &[ |
| 78 | 78 | "TITLE", "AUTHOR", "DATE", "EMAIL", "LANGUAGE", "OPTIONS", "FILETAGS", "DESCRIPTION", |
| 79 | 79 | "KEYWORDS", "CAPTION", "NAME", "ATTR_HTML", "RESULTS", "TBLFM", "INCLUDE", "TODO", |
| @@ -97,20 +97,36 @@ const KNOWN_SCHEMES: &[&str] = &[ |
| 97 | 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 | 106 | impl Audit { |
| 101 | | /// Is this name one the implementation recognizes? |
| 102 | | pub fn is_known(kind: Census, name: &str) -> bool { |
| 103 | | let known = match kind { |
| 104 | | Census::Keyword => KNOWN_KEYWORDS, |
| 107 | /// How the report marks a census name: the recognized set to look the name up in, |
| 108 | /// and what an unlisted name means for that census. |
| 109 | pub fn marker(kind: Census, name: &str) -> &'static str { |
| 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 | 117 | // Every block name renders, and renders as org renders it: the names in |
| 106 | 118 | // `block_construct` through dedicated handling, every other name as a special |
| 107 | 119 | // block — a div carrying the name, holding parsed org, which is exactly what |
| 108 | 120 | // org's exporter emits. No block name is a blind spot. |
| 109 | | Census::Block => return true, |
| 110 | | Census::Drawer => KNOWN_DRAWERS, |
| 111 | | Census::Scheme => KNOWN_SCHEMES, |
| 121 | Census::Block => return HANDLED, |
| 122 | Census::Drawer => (KNOWN_DRAWERS, UNKNOWN), |
| 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 | 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 | 356 | self.count(Scope::In, "timestamp", at); |
| 335 | 357 | } |
| 336 | | if line.contains("{{{") { |
| 358 | if bare.contains("{{{") { |
| 337 | 359 | self.count(Scope::Out, "macro call", at); |
| 338 | 360 | } |
| 339 | | if line.contains("<<<") { |
| 361 | if bare.contains("<<<") { |
| 340 | 362 | self.count(Scope::Out, "radio target", at); |
| 341 | | } else if line.contains("<<") && line.contains(">>") { |
| 363 | } else if bare.contains("<<") && bare.contains(">>") { |
| 342 | 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 | 367 | self.count(Scope::Out, "LaTeX fragment", at); |
| 346 | 368 | } |
| 347 | | if entity_ref(line) { |
| 369 | if entity_ref(&bare) { |
| 348 | 370 | // Rendered, and rendered as org renders it: `\alpha` becomes `α` in |
| 349 | 371 | // both exporters. `fixtures/audit-entities.org` holds the oracle to that. |
| 350 | 372 | self.count(Scope::In, "entity (\\name)", at); |
| @@ -510,11 +532,7 @@ fn latex_inline(line: &str) -> bool { |
| 510 | 532 | /// Only names org knows count, matching [`crate::parser`]'s rule for rendering one: a |
| 511 | 533 | /// Windows path (`C:\Users\me`) and a namespaced identifier (`Tumblr\API\Client`) are not |
| 512 | 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 | 535 | fn entity_ref(line: &str) -> bool { |
| 517 | | let line = without_literal_spans(line); |
| 518 | 536 | let chars: Vec<char> = line.chars().collect(); |
| 519 | 537 | for (i, c) in chars.iter().enumerate() { |
| 520 | 538 | if *c != '\\' { |
| @@ -642,13 +660,9 @@ pub fn report(audit: &Audit) -> String { |
| 642 | 660 | names.sort_by(|a, b| b.1.occurrences.cmp(&a.1.occurrences).then(a.0.cmp(b.0))); |
| 643 | 661 | out.push_str(&format!("\n{title}\n")); |
| 644 | 662 | for (name, tally) in names { |
| 645 | | let flag = if Audit::is_known(kind, name) { |
| 646 | | " " |
| 647 | | } else { |
| 648 | | "??? " |
| 649 | | }; |
| 650 | 663 | out.push_str(&format!( |
| 651 | | "{flag}{:<32} {:>8} {:>7} {}\n", |
| 664 | "{:<4}{:<32} {:>8} {:>7} {}\n", |
| 665 | Audit::marker(kind, name), |
| 652 | 666 | name, |
| 653 | 667 | tally.occurrences, |
| 654 | 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 | 678 | out |
| 661 | 679 | } |