From 9d1bd5bcf80af464b5a9e129e2dc1042a04dca7b Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 6 May 2022 18:35:49 -0700 Subject: [PATCH] Move unindent implementation into indoc to flatten dependency --- Cargo.toml | 4 +- src/lib.rs | 3 +- src/unindent.rs | 131 ++++++++++++++++++++++++++++++++++++++ unindent/src/lib.rs | 132 +-------------------------------------- unindent/src/unindent.rs | 1 + 5 files changed, 137 insertions(+), 134 deletions(-) create mode 100644 src/unindent.rs create mode 120000 unindent/src/unindent.rs diff --git a/Cargo.toml b/Cargo.toml index 88a9190..751d9bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,10 @@ rust-version = "1.42" [lib] proc-macro = true -[dependencies] -unindent = { version = "0.1.8", path = "unindent" } - [dev-dependencies] rustversion = "1.0" trybuild = { version = "1.0.49", features = ["diff"] } +unindent = { version = "0.1.8", path = "unindent" } [workspace] members = ["unindent"] diff --git a/src/lib.rs b/src/lib.rs index 37eff7d..e4d6d22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,14 +111,15 @@ mod error; mod expr; +mod unindent; use crate::error::{Error, Result}; use crate::expr::Expr; +use crate::unindent::unindent; use proc_macro::token_stream::IntoIter as TokenIter; use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; use std::iter::{self, FromIterator}; use std::str::FromStr; -use unindent::unindent; #[derive(Copy, Clone, PartialEq)] enum Macro { diff --git a/src/unindent.rs b/src/unindent.rs new file mode 100644 index 0000000..11d19d2 --- /dev/null +++ b/src/unindent.rs @@ -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 { + // 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) + } + } + } + } +} diff --git a/unindent/src/lib.rs b/unindent/src/lib.rs index 49449c8..3f45df5 100644 --- a/unindent/src/lib.rs +++ b/unindent/src/lib.rs @@ -53,134 +53,6 @@ clippy::type_complexity )] -use std::iter::Peekable; -use std::slice::Split; +mod unindent; -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) - } - } - } - } -} +pub use unindent::*; diff --git a/unindent/src/unindent.rs b/unindent/src/unindent.rs new file mode 120000 index 0000000..ba284e3 --- /dev/null +++ b/unindent/src/unindent.rs @@ -0,0 +1 @@ +../../src/unindent.rs \ No newline at end of file