#[derive(Eq, PartialEq, Copy, Clone)]
#[allow(dead_code)] pub enum Apostrophes {
Handle,
DontHandle,
}
const APOSTROPHE: &str = "\\*(Aq";
#[allow(clippy::doc_markdown)]
pub(crate) const APOSTROPHE_PREABMLE: &str = r#".ie \n(.g .ds Aq \(aq
.el .ds Aq '
"#;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum Escape {
UnescapedAtNewline,
Spaces,
Special,
SpecialNoNewline,
Unescaped,
}
#[cfg(test)]
pub(crate) fn escape_to_string<'a, I>(items: I, ap: Apostrophes) -> String
where
I: IntoIterator<Item = (&'a Escape, &'a str)>,
{
let mut res = Vec::new();
escape(items, &mut res, ap);
String::from_utf8(res).expect("Output should be utf8 by construction")
}
pub(crate) fn escape<'a, I>(items: I, out: &mut Vec<u8>, ap: Apostrophes)
where
I: IntoIterator<Item = (&'a Escape, &'a str)>,
{
let mut at_line_start = true;
for (&meta, payload) in items {
if !at_line_start && meta == Escape::UnescapedAtNewline {
out.push(b'\n');
at_line_start = true;
}
for &c in payload.as_bytes() {
match meta {
Escape::Spaces => {
if c == b' ' || c == b'\n' {
out.extend_from_slice(b"\\ ");
} else {
out.push(c);
}
}
Escape::Special | Escape::SpecialNoNewline => {
if at_line_start && (c == b'.' || c == b'\'') {
out.extend_from_slice(b"\\&");
}
if c == b'\\' || c == b'-' {
out.push(b'\\');
}
if ap == Apostrophes::Handle && c == b'\'' {
out.extend_from_slice(APOSTROPHE.as_bytes());
} else if meta == Escape::SpecialNoNewline && c == b'\n' {
out.push(b' ');
at_line_start = false;
continue;
} else {
out.push(c);
}
}
Escape::Unescaped | Escape::UnescapedAtNewline => {
out.push(c);
}
}
at_line_start = c == b'\n';
}
}
}
#[cfg(test)]
mod test {
use super::{escape_to_string, Apostrophes, Escape};
#[test]
fn sample() {
let ap = Apostrophes::Handle;
let items: &[(Escape, &str)] = &[
(Escape::Unescaped, "\\fI"),
(Escape::Special, "test"),
(Escape::Unescaped, "\\fP"),
];
let output = escape_to_string(items.iter().map(|p| (&p.0, p.1)), ap);
assert_eq!("\\fItest\\fP", output);
}
}