use std::iter::Peekable; use std::slice::Split; pub fn unindent(s: &str) -> String { let bytes = s.as_bytes(); let unindented = unindent_bytes(bytes); String::from_utf8(unindented).unwrap() } // Compute the maximal number of spaces that can be removed from every line, and // remove them. pub fn unindent_bytes(s: &[u8]) -> Vec { // Document may start either on the same line as opening quote or // on the next line let ignore_first_line = s.starts_with(b"\n") || s.starts_with(b"\r\n"); // Largest number of spaces that can be removed from every // non-whitespace-only line after the first let spaces = s .lines() .skip(1) .filter_map(count_spaces) .min() .unwrap_or(0); let mut result = Vec::with_capacity(s.len()); for (i, line) in s.lines().enumerate() { if i > 1 || (i == 1 && !ignore_first_line) { result.push(b'\n'); } if i == 0 { // Do not un-indent anything on same line as opening quote result.extend_from_slice(line); } else if line.len() > spaces { // Whitespace-only lines may have fewer than the number of spaces // being removed result.extend_from_slice(&line[spaces..]); } } result } pub trait Unindent { type Output; fn unindent(&self) -> Self::Output; } impl Unindent for str { type Output = String; fn unindent(&self) -> Self::Output { unindent(self) } } impl Unindent for String { type Output = String; fn unindent(&self) -> Self::Output { unindent(self) } } impl Unindent for [u8] { type Output = Vec; fn unindent(&self) -> Self::Output { unindent_bytes(self) } } impl<'a, T: ?Sized + Unindent> Unindent for &'a T { type Output = T::Output; fn unindent(&self) -> Self::Output { (**self).unindent() } } // Number of leading spaces in the line, or None if the line is entirely spaces. fn count_spaces(line: &[u8]) -> Option { for (i, ch) in line.iter().enumerate() { if *ch != b' ' && *ch != b'\t' { return Some(i); } } None } // Based on core::str::StrExt. trait BytesExt { fn lines(&self) -> Lines; } impl BytesExt for [u8] { fn lines(&self) -> Lines { fn is_newline(b: &u8) -> bool { *b == b'\n' } let bytestring = if self.starts_with(b"\r\n") { &self[1..] } else { self }; Lines { split: bytestring.split(is_newline as fn(&u8) -> bool).peekable(), } } } struct Lines<'a> { split: Peekable bool>>, } impl<'a> Iterator for Lines<'a> { type Item = &'a [u8]; fn next(&mut self) -> Option { match self.split.next() { None => None, Some(fragment) => { if fragment.is_empty() && self.split.peek().is_none() { None } else { Some(fragment) } } } } }