const TICK: u8 = b'`';
pub struct CodeSpans<'a> {
bytes: &'a [u8],
at: usize,
}
impl Iterator for CodeSpans<'_> {
type Item = (usize, usize);
fn next(&mut self) -> Option<(usize, usize)> {
let (start, end) = next_span(self.bytes, self.at)?;
self.at = end;
Some((start, end))
}
}
#[must_use]
pub fn code_spans(body: &str) -> CodeSpans<'_> {
CodeSpans {
bytes: body.as_bytes(),
at: 0,
}
}
#[must_use]
pub fn mask_code_spans(body: &str) -> String {
let mut masked = String::with_capacity(body.len());
let mut cursor = 0;
for (start, end) in code_spans(body) {
masked.push_str(&body[cursor..start]);
for _ in 0..body[start..end].chars().count() {
masked.push(' ');
}
cursor = end;
}
masked.push_str(&body[cursor..]);
masked
}
#[must_use]
pub fn contains_unmasked_pipe(body: &str) -> bool {
let mut cursor = 0;
for (start, end) in code_spans(body) {
if body[cursor..start].contains('|') {
return true;
}
cursor = end;
}
body[cursor..].contains('|')
}
fn next_span(bytes: &[u8], from: usize) -> Option<(usize, usize)> {
let mut i = from;
while i < bytes.len() {
if bytes[i] != TICK {
i += 1;
continue;
}
let mut run = 0;
while bytes.get(i + run) == Some(&TICK) {
run += 1;
}
for k in (1..=run).rev() {
if let Some(end) = find_closer(bytes, i + k, k) {
return Some((i, end));
}
}
i += 1;
}
None
}
fn find_closer(bytes: &[u8], start: usize, k: usize) -> Option<usize> {
let mut j = start;
loop {
if j + k <= bytes.len() && bytes[j..j + k].iter().all(|b| *b == TICK) {
return Some(j + k);
}
if j >= bytes.len() || bytes[j] == b'\n' {
return None;
}
j += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn matched(body: &str) -> Vec<&str> {
code_spans(body).map(|(s, e)| &body[s..e]).collect()
}
#[test]
fn the_approximation_closes_on_the_first_backtick_of_a_longer_run() {
assert_eq!(matched("`a```"), ["`a`", "``"]);
}
#[test]
fn openers_are_tried_longest_first() {
assert_eq!(matched("``` ``` ```"), ["``` ```", "``"]);
assert_eq!(matched("```a`"), ["``", "`a`"]);
}
#[test]
fn a_bare_double_run_is_an_empty_span() {
assert_eq!(matched("``"), ["``"]);
assert_eq!(matched("```"), ["``"]);
assert_eq!(matched("````"), ["````"]);
assert_eq!(matched("`````"), ["````"]);
assert_eq!(matched("``````"), ["``````"]);
}
#[test]
fn a_run_of_two_always_opens_a_span_where_it_starts() {
for tail in ["", "a", "`", "\n", "\\", "a`", "\n`"] {
for run in 2..=4 {
let body = format!("x{}{tail}", "`".repeat(run));
assert_eq!(
code_spans(&body).next().map(|(start, _)| start),
Some(1),
"run of {run} before {tail:?}"
);
}
}
}
#[test]
fn an_unterminated_run_matches_nothing() {
assert_eq!(matched("`a"), Vec::<&str>::new());
assert!(matched("").is_empty());
assert!(matched("no ticks here").is_empty());
}
#[test]
fn a_longer_opener_swallows_a_shorter_inner_run() {
assert_eq!(matched("``a | b` c``"), ["``a | b` c``"]);
assert!(!contains_unmasked_pipe("``a | b` c``"));
assert!(!contains_unmasked_pipe("``|`|``"));
}
#[test]
fn a_bare_pipe_survives_masking() {
assert!(contains_unmasked_pipe("a | b"));
assert!(contains_unmasked_pipe("a `b` | c"));
assert!(contains_unmasked_pipe("|`|`|"));
assert!(!contains_unmasked_pipe("`|`"));
}
#[test]
fn mask_is_char_length_not_byte_length() {
assert_eq!(mask_code_spans("`\u{65e5}\u{672c}`"), " ");
assert_eq!(mask_code_spans("a`b`c"), "a c");
assert_eq!(mask_code_spans("``` ``` ```"), " `");
assert_eq!(mask_code_spans("````a``"), " a ");
assert_eq!(mask_code_spans("`a"), "`a");
}
#[test]
fn a_backslash_does_not_escape_a_backtick() {
assert_eq!(matched("\\`a\\`"), ["`a\\`"]);
}
#[test]
fn a_newline_stops_a_span_and_nothing_else_does() {
assert_eq!(matched("`a\nb`"), Vec::<&str>::new());
assert_eq!(matched("`a\rb`"), ["`a\rb`"]);
assert_eq!(matched("`a\u{2028}b`"), ["`a\u{2028}b`"]);
assert_eq!(matched("`a\u{b}b`"), ["`a\u{b}b`"]);
}
#[test]
fn the_pipe_shortcut_agrees_with_the_mask_it_stands_for() {
for body in [
"", "|", "`|`", "``|`|``", "a | b", "|`a`", "`a`|", "`a|", "```|```", "x``y``z|",
] {
assert_eq!(
contains_unmasked_pipe(body),
mask_code_spans(body).contains('|'),
"disagreed on {body:?}"
);
}
}
}