use super::read::fence_mask;
const ARCHIVE_PREFIX: &str = "TBD - created by archiving change ";
const ARCHIVE_SUFFIX: &str = ". Update Purpose after archive.";
const MARKERS: [&str; 2] = ["TBD", "TODO"];
pub(crate) fn is_placeholder(text: &str) -> bool {
let prose = prose(text);
opens(&prose) || holds_archive_sentence(&prose)
}
pub(crate) fn opens_with_marker(text: &str) -> bool {
opens(&prose(text))
}
fn prose(text: &str) -> String {
let lines: Vec<String> = text.lines().map(str::to_owned).collect();
let fenced = fence_mask(&lines);
let kept: Vec<&str> = lines
.iter()
.zip(fenced)
.filter(|(_, fenced)| !fenced)
.map(|(line, _)| line.as_str())
.collect();
kept.join("\n").trim().to_owned()
}
fn opens(prose: &str) -> bool {
MARKERS.iter().any(|marker| {
let Some(head) = prose.get(..marker.len()) else {
return false;
};
let rest = &prose[marker.len()..];
if rest.chars().next().is_some_and(continues_word) {
return false;
}
head == *marker || (head.eq_ignore_ascii_case(marker) && punctuated(rest))
})
}
fn continues_word(character: char) -> bool {
character.is_alphanumeric() || character == '_'
}
fn punctuated(rest: &str) -> bool {
let rest = rest.trim_start_matches([' ', '\t']);
rest.chars().next().is_none_or(|next| {
matches!(
next,
'\n' | ':'
| '-'
| '\u{2013}'
| '\u{2014}'
| '.'
| ','
| ';'
| '('
| ')'
| '['
| ']'
| '{'
| '}'
)
})
}
fn holds_archive_sentence(prose: &str) -> bool {
prose
.match_indices(ARCHIVE_SUFFIX)
.any(|(suffix, _)| prose[..suffix].contains(ARCHIVE_PREFIX))
}