use crate::lexer::string::{ALIGNMENT, CLOSE, ESCAPE, ESCAPES, FORMAT, OPEN, QUOTE};
pub(super) enum Piece {
Text(String),
Hole {
name: String,
rest: String,
},
}
pub(super) fn pieces(t: &str) -> Option<Vec<Piece>> {
let mut out = Vec::new();
let mut text = String::new();
let mut chars = t.chars().peekable();
while let Some(c) = chars.next() {
if (c == OPEN || c == CLOSE) && chars.peek() == Some(&c) {
chars.next();
text.push(c);
} else if c == OPEN {
let hole: String = chars.by_ref().take_while(|&h| h != CLOSE).collect();
let split = hole.find([ALIGNMENT, FORMAT]).unwrap_or(hole.len());
out.push(Piece::Text(std::mem::take(&mut text)));
out.push(Piece::Hole {
name: hole[..split].trim().to_string(),
rest: hole[split..].to_string(),
});
} else {
text.push(c);
}
}
out.push(Piece::Text(text));
let closed = t.matches(OPEN).count() == t.matches(CLOSE).count();
closed.then_some(out)
}
pub(super) fn names(pieces: &[Piece]) -> Vec<&str> {
let mut names: Vec<&str> = Vec::new();
for p in pieces {
if let Piece::Hole { name, .. } = p
&& !names.iter().any(|n| n.eq_ignore_ascii_case(name))
{
names.push(name);
}
}
names
}
pub(super) fn message(pieces: &[Piece], field: impl Fn(&str) -> String) -> String {
let mut out = String::new();
for p in pieces {
match p {
Piece::Text(t) => out.push_str(&escaped(t)),
Piece::Hole { name, rest } => {
out.push(OPEN);
out.push_str(&field(name));
out.push_str(rest);
out.push(CLOSE);
}
}
}
out
}
pub(super) fn escaped(t: &str) -> String {
let mut out = String::new();
for c in t.chars() {
match ESCAPES.iter().find(|(_, v)| *v == c) {
Some((k, _)) => {
out.push(ESCAPE);
out.push(*k);
}
None if [ESCAPE, QUOTE, OPEN, CLOSE].contains(&c) => {
out.push(ESCAPE);
out.push(c);
}
None => out.push(c),
}
}
out
}