#[cfg(test)] #[macro_use] extern crate galvanic_assert;
pub trait MarginTrimmable {
fn trim_margin_with<M: AsRef<str>>(&self, margin_prefix: M) -> Option<String>;
fn trim_margin(&self) -> Option<String> { self.trim_margin_with("|") }
}
impl<S: AsRef<str>> MarginTrimmable for S {
fn trim_margin_with<M: AsRef<str>>(&self, margin_prefix: M) -> Option<String> {
let lines: Vec<_> = self.as_ref().split('\n').map(|line| line.trim_left()).collect();
if lines.len() <= 1 {
return Some(self.as_ref().into());
}
let mut with_margin: Vec<&str> = Vec::with_capacity(lines.len());
let mut line_iter = lines.into_iter().peekable();
if line_iter.peek().map_or(false, |l| l.is_empty()) {
line_iter.next();
}
let prefix = margin_prefix.as_ref();
while let Some(line) = line_iter.next() {
let is_last_line = line_iter.peek().is_none();
if is_last_line && line.is_empty() {
continue;
}
if !line.starts_with(prefix) {
return None;
}
with_margin.push(&line[prefix.len()..]);
};
Some(with_margin.join("\n"))
}
}
#[cfg(test)]
mod tests {
use galvanic_assert::matchers::*;
use galvanic_assert::matchers::variant::*;
use super::*;
#[test]
fn should_not_modify_empty_string() {
assert_that!(&"".trim_margin(), maybe_some(eq(String::new())));
}
#[test]
fn should_not_modify_single_line_string() {
assert_that!(&"hello, world".trim_margin(), maybe_some(eq("hello, world".into())));
}
#[test]
fn should_trim_margin_of_multiline_string() {
let txt = "|this
| is a
| multiline string
|with margin";
assert_that!(&txt.trim_margin(),
maybe_some(eq(vec!["this", " is a", " multiline string", "with margin"].join("\n"))));
}
#[test]
fn should_remove_first_and_last_line_if_blank() {
let txt = "
|ignore blank
|surrounding lines
";
assert_that!(&txt.trim_margin(),
maybe_some(eq(vec!["ignore blank", "surrounding lines"].join("\n"))));
}
#[test]
fn should_allow_arbitrary_margin_character() {
let txt = "
#ignore blank
#surrounding lines
";
assert_that!(&txt.trim_margin_with("#"),
maybe_some(eq(vec!["ignore blank", "surrounding lines"].join("\n"))));
}
}