Commit 82639e554b
82639e554bcf4a7465c41e6c47effceb501d91d3
parent: 54a241c7fe
Unregistered key
cmc <hello@cleberg.net> · 2026-08-23 03:43 UTC
committer: <noreply@github.com>
audit: count only entity names org knows (#26)
`entity_ref` matched any backslash followed by three or more letters, so
`C:\Users\youruser\Downloads` and `Tumblr\API\Client` were reported as org
entity references. It now consults `entities::lookup` over the full alphabetic
run, which is the rule `parser::try_entity` applies when deciding whether to
render one.
Inline `=verbatim=` and `~code~` spans are blanked before the scan. `=\alpha=`
shows the name rather than rendering the character, so it is not a use of the
feature — source blocks were already skipped for the same reason.
cleberg.net's content drops from 8 out-of-scope uses to 5, and orgo's own docs
from 1 entity hit to none; both corpora contain no real entities.
Closes #23
Layout: unified · split
fixtures/audit-entities.org
added
+3
| @@ -0,0 +1,3 @@ |
| |
1 | #+TITLE: Real entities |
| |
2 | |
| |
3 | The angle \alpha is small, and the arrow \rarr points right. |
fixtures/audit-nonentities.org
added
+7
| @@ -0,0 +1,7 @@ |
| |
1 | #+TITLE: Backslashes that are not entities |
| |
2 | |
| |
3 | Windows paths and namespaced identifiers are not org entities: |
| |
4 | =C:\Users\youruser\Downloads= and Tumblr\API\Client. |
| |
5 | |
| |
6 | Names shown inside verbatim or code are displayed, not rendered, so they are |
| |
7 | not a use of the feature either: =\alpha=, ~\rarr~, =20\deg=. |
src/audit.rs
+38 −6
| @@ -506,21 +506,53 @@ fn latex_inline(line: &str) -> bool { |
| 506 | dollars >= 2 && line.contains("$\\") |
506 | dollars >= 2 && line.contains("$\\") |
| 507 | } |
507 | } |
| 508 | |
508 | |
| 509 | /// A `\name` entity reference such as `\alpha`, excluding LaTeX environment commands. |
509 | /// A `\name` entity reference such as `\alpha`. |
| |
510 | /// |
| |
511 | /// Only names org knows count, matching [`crate::parser`]'s rule for rendering one: a |
| |
512 | /// Windows path (`C:\Users\me`) and a namespaced identifier (`Tumblr\API\Client`) are not |
| |
513 | /// entity references. |
| |
514 | /// |
| |
515 | /// Verbatim and code spans are skipped: `=\alpha=` shows the name rather than rendering |
| |
516 | /// the character, so it is not a use of the feature. |
| 510 | fn entity_ref(line: &str) -> bool { |
517 | fn entity_ref(line: &str) -> bool { |
| 511 | for (i, c) in line.char_indices() { |
518 | let line = without_literal_spans(line); |
| 512 | if c != '\\' { |
519 | let chars: Vec<char> = line.chars().collect(); |
| |
520 | for (i, c) in chars.iter().enumerate() { |
| |
521 | if *c != '\\' { |
| 513 | continue; |
522 | continue; |
| 514 | } |
523 | } |
| 515 | let rest = &line[i + 1..]; |
524 | let name: String = chars[i + 1..].iter().take_while(|c| c.is_ascii_alphabetic()).collect(); |
| 516 | let name: String = rest.chars().take_while(|c| c.is_ascii_alphabetic()).collect(); |
525 | if crate::entities::lookup(&name).is_some() { |
| 517 | if name.len() >= 3 && !matches!(name.as_str(), "begin" | "end") { |
| |
| 518 | return true; |
526 | return true; |
| 519 | } |
527 | } |
| 520 | } |
528 | } |
| 521 | false |
529 | false |
| 522 | } |
530 | } |
| 523 | |
531 | |
| |
532 | /// Blank out `=verbatim=` and `~code~` spans. Deliberately looser than the parser's |
| |
533 | /// border rules — the audit measures prevalence, and erring toward blanking keeps it |
| |
534 | /// from overstating a gap. |
| |
535 | fn without_literal_spans(line: &str) -> String { |
| |
536 | let mut out = String::with_capacity(line.len()); |
| |
537 | let mut open: Option<char> = None; |
| |
538 | for c in line.chars() { |
| |
539 | match open { |
| |
540 | Some(marker) => { |
| |
541 | out.push(' '); |
| |
542 | if c == marker { |
| |
543 | open = None; |
| |
544 | } |
| |
545 | } |
| |
546 | None if c == '=' || c == '~' => { |
| |
547 | open = Some(c); |
| |
548 | out.push(' '); |
| |
549 | } |
| |
550 | None => out.push(c), |
| |
551 | } |
| |
552 | } |
| |
553 | out |
| |
554 | } |
| |
555 | |
| 524 | /// A plausible `*bold*`-style emphasis pair: two markers on one line with non-space |
556 | /// A plausible `*bold*`-style emphasis pair: two markers on one line with non-space |
| 525 | /// content between them. Approximate by design — the audit measures prevalence, and the |
557 | /// content between them. Approximate by design — the audit measures prevalence, and the |
| 526 | /// parser owns the exact pre/post-character rules. |
558 | /// parser owns the exact pre/post-character rules. |
tests/constructs.rs
+31
| @@ -769,3 +769,34 @@ fn footnote_links_and_section_are_labelled() { |
| 769 | "each back-link says where it goes:\n{html}" |
769 | "each back-link says where it goes:\n{html}" |
| 770 | ); |
770 | ); |
| 771 | } |
771 | } |
| |
772 | |
| |
773 | // --------------------------------------------------------------------------- |
| |
774 | // Audit: the entity check counts only what org would actually render |
| |
775 | // --------------------------------------------------------------------------- |
| |
776 | |
| |
777 | fn audit_fixture(name: &str) -> String { |
| |
778 | let path = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures").join(name); |
| |
779 | let audit = orgo::audit::audit(&path).expect("audit fixture"); |
| |
780 | orgo::audit::report(&audit) |
| |
781 | } |
| |
782 | |
| |
783 | /// `\Users` and `\API` are not names org knows, and an entity inside verbatim is shown |
| |
784 | /// rather than rendered. Counting either overstates what the corpus needs. |
| |
785 | #[test] |
| |
786 | fn audit_ignores_backslashes_that_are_not_entities() { |
| |
787 | let report = audit_fixture("audit-nonentities.org"); |
| |
788 | assert!( |
| |
789 | !report.contains("entity (\\name)"), |
| |
790 | "a Windows path or a verbatim-quoted name was counted as an entity:\n{report}" |
| |
791 | ); |
| |
792 | } |
| |
793 | |
| |
794 | /// A bare entity outside verbatim still counts. |
| |
795 | #[test] |
| |
796 | fn audit_counts_real_entities() { |
| |
797 | let report = audit_fixture("audit-entities.org"); |
| |
798 | assert!( |
| |
799 | report.contains("entity (\\name)"), |
| |
800 | "a real entity was not counted:\n{report}" |
| |
801 | ); |
| |
802 | } |