1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Lazy-copying lazy-allocated scanning [`str`] transformations.  
//! This is good e.g. for (un)escaping text, especially if individual strings are short.
//!
//! Note that this library uses [smartstring] (and as such returns [`Woc`]s instead of [`Cow`]s).  
//! The output is still [`Deref<Target = str>`] regardless, so there should be no issue with ease of use.
//!
//! # Example
//!
//! ```rust
//! use {
//!     cervine::Cow,
//!     gnaw::Unshift as _,
//!     lazy_transform_str::{Transform as _, TransformedPart},
//!     smartstring::alias::String,
//! };
//!
//! fn double_a(str: &str) -> Cow<String, str> {
//!     str.transform(|rest /*: &mut &str */| {
//!         // Consume some of the input. `rest` is never empty here.
//!         match rest.unshift().unwrap() {
//!             'a' => TransformedPart::Changed(String::from("aa")),
//!             _ => TransformedPart::Unchanged,
//!         }
//!     } /*: impl FnMut(…) -> … */ )
//! }
//!
//! assert_eq!(double_a("abc"), Cow::Owned(String::from("aabc")));
//! assert_eq!(double_a("bcd"), Cow::Borrowed("bcd"));
//! ```
//!
//! See [`escape_double_quotes`] and [`unescape_backlashed_verbatim`]'s sources for more real-world examples.

#![warn(clippy::pedantic)]

use cervine::Cow;
use gnaw::Unshift as _;
use smartstring::alias::String;

pub enum TransformedPart {
	Unchanged,
	Changed(String),
}

pub fn transform(
	str: &str,
	transform_next: impl FnMut(/* rest: */ &mut &str) -> TransformedPart,
) -> Cow<String, str> {
	str.transform(transform_next)
}

pub trait Transform {
	fn transform(
		&self,
		transform_next: impl FnMut(&mut &str) -> TransformedPart,
	) -> Cow<String, str>;
}

impl Transform for str {
	fn transform(
		&self,
		mut transform_next: impl FnMut(&mut &str) -> TransformedPart,
	) -> Cow<String, str> {
		let mut rest = self;
		let mut copied = loop {
			if rest.is_empty() {
				return Cow::Borrowed(self);
			}
			let unchanged_rest = rest;
			if let TransformedPart::Changed(transformed) = transform_next(&mut rest) {
				let mut copied = String::from(&self[..self.len() - unchanged_rest.len()]);
				copied.push_str(&transformed);
				break copied;
			}
		};

		while !rest.is_empty() {
			let unchanged_rest = rest;
			match transform_next(&mut rest) {
				TransformedPart::Unchanged => {
					copied.push_str(&unchanged_rest[..unchanged_rest.len() - rest.len()]);
				}
				TransformedPart::Changed(changed) => copied.push_str(&changed),
			}
		}

		Cow::Owned(copied)
	}
}

#[must_use = "pure function"]
pub fn escape_double_quotes(string: &str) -> Cow<String, str> {
	string.transform(|rest| match rest.unshift().unwrap() {
		c @ '\\' | c @ '"' => {
			let mut changed = String::from(r"\");
			changed.push(c);
			TransformedPart::Changed(changed)
		}
		_ => TransformedPart::Unchanged,
	})
}

#[must_use = "pure function"]
pub fn unescape_backslashed_verbatim(string: &str) -> Cow<String, str> {
	let mut escaped = false;
	string.transform(|rest| match rest.unshift().unwrap() {
		'\\' if !escaped => {
			escaped = true;
			TransformedPart::Changed(String::new())
		}
		_ => {
			escaped = false;
			TransformedPart::Unchanged
		}
	})
}