pub const MARK: char = '\u{FFFC}';
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Part<'a> {
Text(&'a str),
Paste(&'a str),
}
pub fn parts(text: &str) -> Vec<Part<'_>> {
let mut parts = Vec::new();
let mut rest = text;
while let Some(start) = rest.find(MARK) {
let after = &rest[start + MARK.len_utf8()..];
match after.find(MARK) {
Some(end) => {
if start > 0 {
parts.push(Part::Text(&rest[..start]));
}
parts.push(Part::Paste(&after[..end]));
rest = &after[end + MARK.len_utf8()..];
}
None => {
parts.push(Part::Text(&rest[..start + MARK.len_utf8()]));
rest = after;
}
}
}
if !rest.is_empty() {
parts.push(Part::Text(rest));
}
parts
}
pub fn expand(text: &str) -> String {
let mut full = String::with_capacity(text.len());
for part in parts(text) {
match part {
Part::Text(text) => full.push_str(text),
Part::Paste(payload) => full.push_str(payload),
}
}
full
}
pub fn normalize(text: &str) -> String {
text.replace("\r\n", "\n")
.replace('\r', "\n")
.replace(MARK, "")
}
pub fn embed(input: &str, attachments: &[String]) -> String {
let mut embedded = String::with_capacity(input.len());
let mut payloads = attachments.iter();
for (index, piece) in input.split(MARK).enumerate() {
if index > 0 {
if let Some(payload) = payloads.next() {
let payload = normalize(payload);
embedded.push(MARK);
embedded.push_str(&payload);
embedded.push(MARK);
}
}
embedded.push_str(piece);
}
embedded
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embed_expand_round_trips() {
let long = "l1\nl2\nl3\nl4";
assert_eq!(
embed(&format!("look at {MARK} this"), &[long.to_owned()]),
format!("look at {MARK}{long}{MARK} this")
);
let embedded = embed(&format!("a{MARK}b"), &[long.to_owned()]);
assert_eq!(embedded, format!("a{MARK}{long}{MARK}b"));
assert_eq!(expand(&embedded), format!("a{long}b"));
let wrapped = embed(
&format!("{MARK}{MARK}b"),
&["p1".to_owned(), "p2".to_owned()],
);
assert_eq!(wrapped, format!("{MARK}p1{MARK}{MARK}p2{MARK}b"));
assert_eq!(expand(&wrapped), "p1p2b");
}
#[test]
fn parts_scans_marks_and_payloads() {
let embedded = format!("x{MARK}p1{MARK}y{MARK}p2{MARK}");
assert_eq!(
parts(&embedded),
vec![
Part::Text("x"),
Part::Paste("p1"),
Part::Text("y"),
Part::Paste("p2"),
]
);
assert_eq!(parts("plain"), vec![Part::Text("plain")]);
assert!(parts("").is_empty());
}
#[test]
fn an_unpaired_mark_stays_literal() {
let stray = format!("a{MARK}b");
assert_eq!(
parts(&stray),
vec![Part::Text(&format!("a{MARK}")), Part::Text("b")]
);
assert_eq!(expand(&stray), stray);
}
#[test]
fn embed_normalizes_payloads() {
let embedded = embed(
&format!("x{MARK}"),
&["p\r\nq".to_owned(), format!("r{MARK}s")],
);
assert_eq!(embedded, format!("x{MARK}p\nq{MARK}"));
assert_eq!(expand(&embedded), "xp\nq");
}
}