#[must_use]
pub(crate) fn slugify(s: &str) -> String {
let mut out = String::new();
let mut prev_dash = false;
for c in s.chars() {
if c.is_ascii_alphanumeric() {
out.push(c.to_ascii_lowercase());
prev_dash = false;
} else if !prev_dash {
out.push('-');
prev_dash = true;
}
}
out.trim_matches('-').to_owned()
}
#[must_use]
pub(crate) fn scan_wiki_links(line: &str) -> Vec<String> {
let mut out = Vec::new();
let stripped = strip_code_spans(line);
let mut rest = stripped.as_str();
while let Some(open) = rest.find("[[") {
let after = &rest[open + 2..];
if let Some(close) = after.find("]]") {
let inner = after[..close].trim();
if !inner.is_empty() {
out.push(inner.to_owned());
}
rest = &after[close + 2..];
} else {
break;
}
}
out
}
#[must_use]
pub(crate) fn strip_code_spans(line: &str) -> String {
let bytes = line.as_bytes();
let mut out = String::new();
let mut i = 0;
while i < bytes.len() {
if bytes[i] != b'`' {
let start = i;
while i < bytes.len() && bytes[i] != b'`' {
i += 1;
}
out.push_str(&line[start..i]);
continue;
}
let run_start = i;
while i < bytes.len() && bytes[i] == b'`' {
i += 1;
}
let run = i - run_start;
let mut j = i;
let mut close = None;
while j < bytes.len() {
if bytes[j] == b'`' {
let s = j;
while j < bytes.len() && bytes[j] == b'`' {
j += 1;
}
if j - s == run {
close = Some(j);
break;
}
} else {
j += 1;
}
}
match close {
Some(end) => i = end,
None => out.push_str(&line[run_start..i]),
}
}
out
}
#[cfg(test)]
mod tests {
use super::strip_code_spans;
#[test]
fn removes_single_and_multi_backtick_spans() {
assert_eq!(strip_code_spans("a `code` b"), "a b");
assert_eq!(strip_code_spans("see ``@rto:0001`` here"), "see here");
assert_eq!(strip_code_spans("x ```fenced inline``` y"), "x y");
}
#[test]
fn keeps_unmatched_backticks_and_plain_text() {
assert_eq!(strip_code_spans("no code here"), "no code here");
assert_eq!(strip_code_spans("unmatched ` tick"), "unmatched ` tick");
assert_eq!(strip_code_spans("``open ` mid"), "``open ` mid");
}
#[test]
fn preserves_utf8_outside_spans() {
assert_eq!(strip_code_spans("café `x` — ok"), "café — ok");
}
}