Skip to content

Commit

Permalink
fix(sub)!: Abstract away substitution values
Browse files Browse the repository at this point in the history
This will give us more implementation flexibility
  • Loading branch information
epage committed Apr 20, 2024
1 parent afa5561 commit 0bd0a96
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 21 deletions.
1 change: 1 addition & 0 deletions crates/snapbox/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ pub use data::Data;
pub use data::ToDebug;
pub use error::Error;
pub use snapbox_macros::debug;
pub use substitutions::SubstitutionValue;
pub use substitutions::Substitutions;

pub type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down
97 changes: 78 additions & 19 deletions crates/snapbox/src/substitutions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ use std::borrow::Cow;
/// Built-in expressions:
/// - `...` on a line of its own: match multiple complete lines
/// - `[..]`: match multiple characters within a line
#[derive(Default, Clone, Debug, PartialEq, Eq)]
#[derive(Default, Clone, Debug)]
pub struct Substitutions {
vars: std::collections::BTreeMap<&'static str, std::collections::BTreeSet<Cow<'static, str>>>,
unused: std::collections::BTreeSet<&'static str>,
vars: std::collections::BTreeMap<
&'static str,
std::collections::BTreeSet<SubstitutionValueInner>,
>,
unused: std::collections::BTreeSet<SubstitutionValueInner>,
}

impl Substitutions {
Expand All @@ -35,17 +38,14 @@ impl Substitutions {
pub fn insert(
&mut self,
key: &'static str,
value: impl Into<Cow<'static, str>>,
value: impl Into<SubstitutionValue>,
) -> Result<(), crate::Error> {
let key = validate_key(key)?;
let value = value.into();
if value.is_empty() {
self.unused.insert(key);
if let Some(inner) = value.inner {
self.vars.entry(key).or_default().insert(inner);
} else {
self.vars
.entry(key)
.or_default()
.insert(crate::utils::normalize_text(value.as_ref()).into());
self.unused.insert(SubstitutionValueInner::Str(key));
}
Ok(())
}
Expand All @@ -55,7 +55,7 @@ impl Substitutions {
/// keys must be enclosed in `[` and `]`.
pub fn extend(
&mut self,
vars: impl IntoIterator<Item = (&'static str, impl Into<Cow<'static, str>>)>,
vars: impl IntoIterator<Item = (&'static str, impl Into<SubstitutionValue>)>,
) -> Result<(), crate::Error> {
for (key, value) in vars {
self.insert(key, value)?;
Expand Down Expand Up @@ -88,35 +88,94 @@ impl Substitutions {
let mut input = input.to_owned();
replace_many(
&mut input,
self.vars.iter().flat_map(|(var, replaces)| {
replaces.iter().map(|replace| (replace.as_ref(), *var))
}),
self.vars
.iter()
.flat_map(|(var, replaces)| replaces.iter().map(|replace| (replace, *var))),
);
input
}

fn clear<'v>(&self, pattern: &'v str) -> Cow<'v, str> {
if !self.unused.is_empty() && pattern.contains('[') {
let mut pattern = pattern.to_owned();
replace_many(&mut pattern, self.unused.iter().map(|var| (*var, "")));
replace_many(&mut pattern, self.unused.iter().map(|var| (var, "")));
Cow::Owned(pattern)
} else {
Cow::Borrowed(pattern)
}
}
}

#[derive(Clone)]
pub struct SubstitutionValue {
inner: Option<SubstitutionValueInner>,
}

#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
enum SubstitutionValueInner {
Str(&'static str),
String(String),
}

impl SubstitutionValueInner {
fn find_in(&self, buffer: &str) -> Option<std::ops::Range<usize>> {
match self {
Self::Str(s) => buffer.find(s).map(|offset| offset..(offset + s.len())),
Self::String(s) => buffer.find(s).map(|offset| offset..(offset + s.len())),
}
}
}

impl From<&'static str> for SubstitutionValue {
fn from(inner: &'static str) -> Self {
if inner.is_empty() {
Self { inner: None }
} else {
Self {
inner: Some(SubstitutionValueInner::Str(inner)),
}
}
}
}

impl From<String> for SubstitutionValue {
fn from(inner: String) -> Self {
if inner.is_empty() {
Self { inner: None }
} else {
Self {
inner: Some(SubstitutionValueInner::String(inner)),
}
}
}
}

impl From<&'_ String> for SubstitutionValue {
fn from(inner: &'_ String) -> Self {
inner.clone().into()
}
}

impl From<Cow<'static, str>> for SubstitutionValue {
fn from(inner: Cow<'static, str>) -> Self {
match inner {
Cow::Borrowed(s) => s.into(),
Cow::Owned(s) => s.into(),
}
}
}

/// Replacements is `(from, to)`
fn replace_many<'a>(
buffer: &mut String,
replacements: impl IntoIterator<Item = (&'a str, &'a str)>,
replacements: impl IntoIterator<Item = (&'a SubstitutionValueInner, &'a str)>,
) {
for (var, replace) in replacements {
let mut index = 0;
while let Some(offset) = buffer[index..].find(var) {
let old_range = (index + offset)..(index + offset + var.len());
while let Some(offset) = var.find_in(&buffer[index..]) {
let old_range = (index + offset.start)..(index + offset.end);
buffer.replace_range(old_range, replace);
index += offset + replace.len();
index += offset.start + replace.len();
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl TestCases {
var: &'static str,
value: impl Into<Cow<'static, str>>,
) -> Result<&Self, crate::Error> {
self.substitutions.borrow_mut().insert(var, value)?;
self.substitutions.borrow_mut().insert(var, value.into())?;
Ok(self)
}

Expand All @@ -148,7 +148,9 @@ impl TestCases {
&self,
vars: impl IntoIterator<Item = (&'static str, impl Into<Cow<'static, str>>)>,
) -> Result<&Self, crate::Error> {
self.substitutions.borrow_mut().extend(vars)?;
self.substitutions
.borrow_mut()
.extend(vars.into_iter().map(|(v, r)| (v, r.into())))?;
Ok(self)
}

Expand Down

0 comments on commit 0bd0a96

Please sign in to comment.