use regex::Regex;
lazy_static! {
static ref SINGLE_QUOTES: Replacer = Replacer::RegexSurround {
regex: Regex::new(r"`(.*?)'").unwrap(),
begin: "\u{2018}",
end: "\u{2019}",
};
static ref DOUBLE_QUOTES: Replacer = Replacer::RegexSurround {
regex: Regex::new(r"``(.*?)''").unwrap(),
begin: "\u{201c}",
end: "\u{201d}",
};
static ref LOW_DOUBLE_QUOTES: Replacer = Replacer::RegexSurround {
regex: Regex::new(r",,(.*?)''").unwrap(),
begin: "\u{201e}",
end: "\u{201d}",
};
static ref ELLIPSIS: Replacer = Replacer::RegexReplace {
regex: Regex::new(r"(?:\.\.\.|\. \. \.)").unwrap(),
replacement: "\u{2026}",
};
}
#[derive(Debug)]
pub enum Replacer {
RegexReplace {
regex: Regex,
replacement: &'static str,
},
RegexSurround {
regex: Regex,
begin: &'static str,
end: &'static str,
},
}
impl Replacer {
fn replace(&self, log: &slog::Logger, text: &mut String, buffer: &mut String) {
use self::Replacer::*;
match *self {
RegexReplace {
ref regex,
replacement,
} => {
debug!(
log,
"Running regular expression replacement";
"type" => "regex",
"pattern" => regex.as_str(),
"replacement" => replacement,
);
while let Some(capture) = regex.captures(text) {
let range = {
let mtch = capture
.get(0)
.expect("Regular expression lacks a full match");
mtch.start()..mtch.end()
};
text.replace_range(range, replacement);
}
}
RegexSurround {
ref regex,
begin,
end,
} => {
debug!(
log,
"Running regular expression capture replacement";
"type" => "surround",
"pattern" => regex.as_str(),
"begin" => begin,
"end" => end,
);
while let Some(capture) = regex.captures(text) {
let mtch = capture
.get(1)
.expect("Regular expression lacks a content group");
let range = {
let mtch = capture
.get(0)
.expect("Regular expression lacks a full match");
mtch.start()..mtch.end()
};
buffer.clear();
buffer.push_str(begin);
buffer.push_str(mtch.as_str());
buffer.push_str(end);
text.replace_range(range, &buffer);
}
}
}
}
}
pub fn substitute(log: &slog::Logger, text: &mut String) {
let mut buffer = String::new();
debug!(log, "Performing typography substitutions"; "text" => &*text);
macro_rules! replace {
($replacer:expr) => {
$replacer.replace(log, text, &mut buffer)
};
}
replace!(DOUBLE_QUOTES);
replace!(LOW_DOUBLE_QUOTES);
replace!(SINGLE_QUOTES);
replace!(ELLIPSIS);
}
#[cfg(test)]
const TEST_CASES: [(&str, &str); 3] = [
(
"John laughed. ``You'll never defeat me!''\n``That's where you're wrong...''",
"John laughed. “You'll never defeat me!”\n“That's where you're wrong…”",
),
(
",,あんたはばかです!''\n``Ehh?''\n,,ほんと!''\n[[footnoteblock]]",
"„あんたはばかです!”\n“Ehh?”\n„ほんと!”\n[[footnoteblock]]",
),
(
"**ENTITY MAKES DRAMATIC MOTION** . . . ",
"**ENTITY MAKES DRAMATIC MOTION** … ",
),
];
#[test]
fn regexes() {
let _ = &*SINGLE_QUOTES;
let _ = &*DOUBLE_QUOTES;
let _ = &*LOW_DOUBLE_QUOTES;
let _ = &*ELLIPSIS;
}
#[test]
fn test_substitute() {
use super::test::test_substitution;
test_substitution("typography", substitute, &TEST_CASES);
}