Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from dtolnay/dep
Move unindent implementation into indoc to flatten dependency
- Loading branch information
Showing
5 changed files
with
145 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
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<u8> { | ||
// 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<u8>; | ||
|
||
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<usize> { | ||
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<Split<'a, u8, fn(&u8) -> bool>>, | ||
} | ||
|
||
impl<'a> Iterator for Lines<'a> { | ||
type Item = &'a [u8]; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
match self.split.next() { | ||
None => None, | ||
Some(fragment) => { | ||
if fragment.is_empty() && self.split.peek().is_none() { | ||
None | ||
} else { | ||
Some(fragment) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../src/unindent.rs |