#[must_use]
pub fn build_match_text(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut in_fence = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("```") || trimmed.starts_with("~~~") {
in_fence = !in_fence;
continue;
}
if in_fence {
continue;
}
if trimmed.starts_with('#') {
continue;
}
{
let s = trimmed.strip_prefix('>').map_or("", str::trim);
if s.starts_with("[!") {
continue;
}
}
let cleaned = strip_inline(line);
if !cleaned.is_empty() {
if !out.is_empty() {
out.push(' ');
}
out.push_str(&cleaned);
}
}
if out.chars().count() <= 80 {
out
} else {
out.chars().take(80).collect()
}
}
fn strip_inline(line: &str) -> String {
let s = line.trim();
let s = s.strip_prefix('>').map_or(s, str::trim);
let s = strip_list_marker(s);
let s = strip_task_checkbox(s);
let mut result = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
match c {
'<' => {
skip_until(&mut chars, '>');
}
'!' => {
if chars.peek() == Some(&'[') {
chars.next(); if chars.peek() == Some(&'[') {
chars.next();
skip_until_double(&mut chars, ']');
} else {
skip_until(&mut chars, ']');
if chars.peek() == Some(&'(') {
chars.next();
skip_until(&mut chars, ')');
}
}
} else {
result.push(c);
}
}
'[' => {
if chars.peek() == Some(&'[') {
chars.next(); let inner = collect_until_double(&mut chars, ']');
let display = inner
.split_once('|')
.map_or(inner.as_str(), |(_, alias)| alias.trim());
result.push_str(display);
} else if chars.peek() == Some(&'^') {
skip_until(&mut chars, ']');
} else {
let text = collect_until(&mut chars, ']');
result.push_str(&text);
if chars.peek() == Some(&'(') {
chars.next();
skip_until(&mut chars, ')');
}
}
}
'*' | '_' => {
if chars.peek() == Some(&c) {
chars.next(); }
}
'`' => {
let delim = if chars.peek() == Some(&'`') {
chars.next();
if chars.peek() == Some(&'`') {
chars.next();
"```"
} else {
"``"
}
} else {
"`"
};
let code = collect_until_str(&mut chars, delim);
result.push_str(&code);
}
other => result.push(other),
}
}
result.split_whitespace().collect::<Vec<_>>().join(" ")
}
fn strip_list_marker(s: &str) -> &str {
if let Some(rest) = s
.strip_prefix("- ")
.or_else(|| s.strip_prefix("* "))
.or_else(|| s.strip_prefix("+ "))
{
return rest;
}
let mut end = 0;
for (i, c) in s.char_indices() {
if c.is_ascii_digit() {
end = i + 1;
} else {
break;
}
}
if end > 0
&& let Some(rest) = s[end..].strip_prefix(". ")
{
return rest;
}
s
}
fn strip_task_checkbox(s: &str) -> &str {
if s.starts_with('[') && s.len() >= 4 {
let third = s.as_bytes().get(2).copied();
let fourth = s.as_bytes().get(3).copied();
if s.as_bytes().get(1).copied() != Some(b']')
&& s.as_bytes().get(1).copied().is_some()
&& third == Some(b']')
&& fourth == Some(b' ')
{
return &s[4..];
}
}
s
}
fn skip_until<I: Iterator<Item = char>>(chars: &mut I, end: char) {
for c in chars.by_ref() {
if c == end {
break;
}
}
}
fn skip_until_double<I: Iterator<Item = char>>(chars: &mut std::iter::Peekable<I>, end: char) {
while let Some(c) = chars.next() {
if c == end && chars.peek() == Some(&end) {
chars.next();
break;
}
}
}
fn collect_until<I: Iterator<Item = char>>(chars: &mut I, end: char) -> String {
let mut buf = String::new();
for c in chars.by_ref() {
if c == end {
break;
}
buf.push(c);
}
buf
}
fn collect_until_double<I: Iterator<Item = char>>(
chars: &mut std::iter::Peekable<I>,
end: char,
) -> String {
let mut buf = String::new();
while let Some(c) = chars.next() {
if c == end && chars.peek() == Some(&end) {
chars.next();
break;
}
buf.push(c);
}
buf
}
fn collect_until_str<I: Iterator<Item = char>>(
chars: &mut std::iter::Peekable<I>,
delim: &str,
) -> String {
let mut buf = String::new();
let end_char = delim.chars().next().unwrap_or('`');
while let Some(c) = chars.next() {
if c == end_char {
for _ in 1..delim.chars().count() {
if chars.peek() == Some(&end_char) {
chars.next();
}
}
break;
}
buf.push(c);
}
buf
}
#[cfg(test)]
mod tests;