pub const WORD_BOUNDARY: &str = "\u{2581}";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Prefix {
None,
Always,
WhenAbsent,
}
pub fn escape(text: &str, prefix: Prefix, collapse_runs: bool) -> String {
let mut out = String::new();
escape_into(text, prefix, collapse_runs, &mut out);
out
}
pub fn escape_into(text: &str, prefix: Prefix, collapse_runs: bool, out: &mut String) {
out.clear();
out.reserve(text.len() + WORD_BOUNDARY.len());
let prepend = match prefix {
Prefix::None => false,
Prefix::Always => true,
Prefix::WhenAbsent => !(text.starts_with(' ') || text.starts_with(WORD_BOUNDARY)),
};
if prepend {
out.push_str(WORD_BOUNDARY);
}
let mut prev_space = false;
for ch in text.chars() {
if ch == ' ' {
if collapse_runs && prev_space {
continue;
}
prev_space = true;
out.push_str(WORD_BOUNDARY);
} else {
prev_space = false;
out.push(ch);
}
}
}
pub fn for_each_segment<'a>(escaped: &'a str, mut f: impl FnMut(&'a str)) {
let marker = WORD_BOUNDARY.as_bytes();
let bytes = escaped.as_bytes();
let mut start = 0;
let mut at = 0;
while at + marker.len() <= bytes.len() {
if bytes[at..at + marker.len()] != *marker {
at += 1;
continue;
}
if let Some(segment) = escaped.get(start..at).filter(|s| !s.is_empty()) {
f(segment);
}
start = at;
at += marker.len();
}
if let Some(rest) = escaped.get(start..).filter(|s| !s.is_empty()) {
f(rest);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn segments(escaped: &str) -> Vec<&str> {
let mut out = Vec::new();
for_each_segment(escaped, |segment| out.push(segment));
out
}
#[test]
fn spaces_become_markers_and_are_never_dropped() {
assert_eq!(escape("a b", Prefix::None, false), "a▁b");
assert_eq!(escape(" ", Prefix::None, false), "▁");
assert_eq!(escape("a ", Prefix::None, false), "a▁");
}
#[test]
fn prefix_conventions_differ_on_an_already_marked_start() {
assert_eq!(escape(" a", Prefix::Always, false), "▁▁a");
assert_eq!(escape("a", Prefix::Always, false), "▁a");
assert_eq!(escape(" a", Prefix::WhenAbsent, false), "▁a");
assert_eq!(escape("a", Prefix::WhenAbsent, false), "▁a");
assert_eq!(escape("", Prefix::WhenAbsent, false), "▁");
assert_eq!(escape("", Prefix::None, false), "");
}
#[test]
fn collapsing_runs_merges_only_spaces() {
assert_eq!(escape("a b", Prefix::None, true), "a▁b");
assert_eq!(escape(" ", Prefix::WhenAbsent, true), "▁");
assert_eq!(escape(" ", Prefix::WhenAbsent, false), "▁▁▁");
assert_eq!(escape("a\n\nb", Prefix::None, true), "a\n\nb");
}
#[test]
fn segments_start_at_each_marker() {
assert_eq!(segments("▁a▁b"), vec!["▁a", "▁b"]);
assert_eq!(segments("▁a▁"), vec!["▁a", "▁"]);
assert_eq!(segments("▁"), vec!["▁"]);
assert_eq!(segments("▁▁a"), vec!["▁", "▁a"]);
assert_eq!(segments("a▁b"), vec!["a", "▁b"]);
assert!(segments("").is_empty());
assert_eq!(segments("abc"), vec!["abc"]);
}
}