extern crate alloc;
use alloc::{borrow::ToOwned, string::String};
pub trait InfraStr {
fn normalize_newlines(&self) -> String;
fn strip_newlines(&self) -> String;
fn trim_ascii_whitespace(&self) -> &str;
fn trim_collapse_ascii_whitespace(&self) -> String;
fn collect_codepoints<P>(&self, position: &mut usize, predicate: P) -> String
where
P: Fn(char) -> bool;
fn skip_codepoints<P>(&self, position: &mut usize, predicate: P)
where
P: Fn(char) -> bool;
fn skip_ascii_whitespace(&self, position: &mut usize);
}
impl InfraStr for str {
fn normalize_newlines(&self) -> String {
normalize_newlines(self)
}
fn strip_newlines(&self) -> String {
strip_newlines(self)
}
fn trim_ascii_whitespace(&self) -> &str {
trim_ascii_whitespace(self)
}
fn trim_collapse_ascii_whitespace(&self) -> String {
trim_collapse_ascii_whitespace(self)
}
fn collect_codepoints<P>(&self, position: &mut usize, predicate: P) -> String
where
P: Fn(char) -> bool,
{
collect_codepoints(self, position, predicate)
}
fn skip_codepoints<P>(&self, position: &mut usize, predicate: P)
where
P: Fn(char) -> bool,
{
skip_codepoints(self, position, predicate)
}
fn skip_ascii_whitespace(&self, position: &mut usize) {
skip_ascii_whitespace(self, position)
}
}
impl InfraStr for String {
fn normalize_newlines(&self) -> String {
normalize_newlines(self.as_str())
}
fn strip_newlines(&self) -> String {
strip_newlines(self.as_str())
}
fn trim_ascii_whitespace(&self) -> &str {
trim_ascii_whitespace(self.as_str())
}
fn trim_collapse_ascii_whitespace(&self) -> String {
trim_collapse_ascii_whitespace(self.as_str())
}
fn collect_codepoints<P>(&self, position: &mut usize, predicate: P) -> String
where
P: Fn(char) -> bool,
{
collect_codepoints(self.as_str(), position, predicate)
}
fn skip_codepoints<P>(&self, position: &mut usize, predicate: P)
where
P: Fn(char) -> bool,
{
skip_codepoints(self.as_str(), position, predicate)
}
fn skip_ascii_whitespace(&self, position: &mut usize) {
skip_ascii_whitespace(self.as_str(), position)
}
}
#[must_use]
#[inline]
pub fn normalize_newlines(s: &str) -> String {
s.replace("\u{000D}\u{000A}", "\u{000A}")
.as_str()
.replace('\u{000D}', "\u{000A}")
}
#[must_use]
#[inline]
pub fn strip_newlines(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut stripped_codepoints = 0usize;
for c in s.chars() {
if c != '\u{000A}' && c != '\u{000D}' {
result.push(c);
stripped_codepoints += 1usize;
}
}
if result.len() != s.len() {
result.shrink_to(s.len() - stripped_codepoints);
}
result
}
#[must_use]
pub fn trim_ascii_whitespace(s: &str) -> &str {
s.trim_matches(|c: char| c.is_ascii_whitespace())
}
#[must_use]
pub fn trim_collapse_ascii_whitespace(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut last_seen_whitespace = false;
for c in s.chars() {
if c.is_ascii_whitespace() {
if !last_seen_whitespace {
last_seen_whitespace = true;
result.push('\u{0020}');
continue;
}
} else {
last_seen_whitespace = false;
result.push(c);
}
}
trim_ascii_whitespace(result.as_str()).to_owned()
}
pub fn collect_codepoints<P>(s: &str, position: &mut usize, predicate: P) -> String
where
P: Fn(char) -> bool,
{
if s.is_empty() || position >= &mut s.len() {
return String::new();
}
let mut result = String::with_capacity(s.len() - *position);
let starting_position = *position;
skip_codepoints(s, position, predicate);
result.push_str(&s[starting_position..*position]);
if result.len() < s.len() - *position {
result.shrink_to_fit();
}
result
}
pub fn skip_codepoints<P>(s: &str, position: &mut usize, predicate: P)
where
P: Fn(char) -> bool,
{
if s.is_empty() || position >= &mut s.len() {
return;
}
let rest = s.chars().skip(*position);
for c in rest {
if position < &mut s.len() && predicate(c) {
*position += 1;
} else {
break;
}
}
}
pub fn skip_ascii_whitespace(s: &str, position: &mut usize) {
skip_codepoints(s, position, |c| c.is_ascii_whitespace())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_normalize_newlines() {
assert_eq!(
"\ralice\r\n\r\nbob\r".normalize_newlines(),
String::from("\nalice\n\nbob\n")
);
}
#[test]
fn test_strip_newlines_empty() {
assert_eq!("\r\r\n\n\r\n".strip_newlines(), String::from(""));
}
#[test]
fn test_strip_newlines_empty2() {
assert_eq!("".strip_newlines(), String::new());
}
#[test]
fn test_strip_newlines_strings1() {
assert_eq!("Alice\n\rBob".strip_newlines(), String::from("AliceBob"));
}
#[test]
fn test_trim_ascii_whitespace_empty() {
assert_eq!(" ".trim_ascii_whitespace(), String::from(""));
}
#[test]
fn test_trim_ascii_whitespace_strings1() {
assert_eq!(
" cats and dogs ".trim_ascii_whitespace(),
String::from("cats and dogs")
);
}
#[test]
fn test_trim_collapse_ascii_whitespace() {
assert_eq!(
"\r \n cat dog hamster".trim_collapse_ascii_whitespace(),
String::from("cat dog hamster")
);
}
#[test]
fn test_collect_codepoints_empty() {
let mut position = 0usize;
let collected = "".collect_codepoints(&mut position, |c| c.is_ascii_whitespace());
assert_eq!(collected, String::new());
}
#[test]
fn test_collect_codepoints_high_position() {
let mut position = 15usize;
let collected = "alice".collect_codepoints(&mut position, |c| c.is_alphabetic());
assert_eq!(collected, String::new());
}
#[test]
fn test_collect_codepoints_string2() {
let test = "test!!!!!";
let mut position = 0usize;
let collected = test.collect_codepoints(&mut position, |c| c.is_ascii_alphabetic());
assert_eq!(collected, String::from("test"));
assert_eq!(position, 4);
}
#[test]
fn test_collect_codepoints_either() {
let value = "Apple Banana Orange";
let mut position = 0usize;
let collected = collect_codepoints(value, &mut position, |c| {
c.is_alphabetic() || c.is_whitespace()
});
assert_eq!(collected, String::from("Apple Banana Orange"));
}
#[test]
fn skip_codepoints() {
let s = "1234test";
let mut position = 0usize;
s.skip_codepoints(&mut position, |c| c.is_ascii_digit());
assert_eq!(position, 4);
assert_eq!(&s[position..], "test");
}
#[test]
fn skip_codepoints_no_matches_early_exit() {
let s = "1234test";
let mut position = 0usize;
s.skip_codepoints(&mut position, |c| c.is_ascii_alphabetic());
assert_eq!(position, 0);
assert_eq!(&s[position..], "1234test");
}
#[test]
fn skip_codepoints_match_until_end() {
let s = "123456789";
let mut position = 0usize;
s.skip_codepoints(&mut position, |c| c.is_ascii_digit());
assert_eq!(position, 9);
assert_eq!(&s[position..], "");
}
#[test]
fn skip_codepoints_empty_str() {
let s = "";
let mut position = 0usize;
s.skip_codepoints(&mut position, |c| c.is_ascii_digit());
assert_eq!(position, 0);
assert_eq!(&s[position..], "");
}
#[test]
fn skip_ascii_whitespace() {
let s = " test";
let mut position = 0usize;
s.skip_ascii_whitespace(&mut position);
assert_eq!(position, 3);
assert_eq!(&s[position..], "test");
}
#[test]
fn impl_infrastr_for_string() {
assert_eq!(
String::from("\ralice\r\n\r\nbob\r").normalize_newlines(),
String::from("\nalice\n\nbob\n")
);
assert_eq!(
String::from("Alice\n\rBob").strip_newlines(),
String::from("AliceBob")
);
assert_eq!(
String::from(" ").trim_ascii_whitespace(),
String::from("")
);
assert_eq!(
String::from("\r \n cat dog hamster").trim_collapse_ascii_whitespace(),
String::from("cat dog hamster")
);
{
let test = String::from("test!!!!!");
let mut position = 0usize;
let collected =
test.collect_codepoints(&mut position, |c| c.is_ascii_alphabetic());
assert_eq!(collected, String::from("test"));
assert_eq!(position, 4);
}
{
let s = String::from("1234test");
let mut position = 0usize;
s.skip_codepoints(&mut position, |c| c.is_ascii_digit());
assert_eq!(position, 4);
assert_eq!(&s[position..], "test");
}
{
let s = String::from(" test");
let mut position = 0usize;
s.skip_ascii_whitespace(&mut position);
assert_eq!(position, 3);
assert_eq!(&s[position..], "test");
}
}
}