mod error;
use crate::{replacement::ReplacementProvider, token::PatternToken};
pub use error::InterpolatorError;
use std::{
borrow::Cow,
fmt::{Debug, Display, Formatter},
str::FromStr,
};
use writeable::Writeable;
#[derive(Debug, PartialEq)]
pub enum InterpolatedKind<'i, 's, E> {
Literal(&'i Cow<'s, str>),
Element(&'i E),
}
impl<'i, 's, E> Writeable for InterpolatedKind<'i, 's, E>
where
E: Writeable,
{
fn write_to<W>(&self, sink: &mut W) -> std::result::Result<(), std::fmt::Error>
where
W: std::fmt::Write + ?Sized,
{
match self {
Self::Literal(lit) => sink.write_str(lit),
Self::Element(elem) => elem.write_to(sink),
}
}
}
impl<'i, 's, E> Display for InterpolatedKind<'i, 's, E>
where
E: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::Literal(lit) => f.write_str(lit),
Self::Element(elem) => elem.fmt(f),
}
}
}
type Result<E, R> = std::result::Result<Option<E>, InterpolatorError<R>>;
pub struct Interpolator<'i, 'p, R, E>
where
R: ReplacementProvider<'i, E>,
{
tokens: &'i [PatternToken<'p, R::Key>],
token_idx: usize,
replacements: &'i R,
current_replacement: Option<R::Iter>,
}
impl<'i, 'p, R, E> Interpolator<'i, 'p, R, E>
where
R: ReplacementProvider<'i, E>,
{
pub fn new(tokens: &'i [PatternToken<'p, R::Key>], replacements: &'i R) -> Self {
Self {
tokens,
token_idx: 0,
replacements,
current_replacement: None,
}
}
pub fn try_next(&mut self) -> Result<InterpolatedKind<'i, 'p, E>, R::Key>
where
R::Key: Debug + FromStr + PartialEq + Clone,
<R::Key as FromStr>::Err: Debug + PartialEq,
{
loop {
if let Some(ref mut replacement) = &mut self.current_replacement {
if let Some(v) = replacement.next() {
return Ok(Some(InterpolatedKind::Element(v)));
} else {
self.current_replacement = None;
}
}
match self.tokens.get(self.token_idx) {
Some(&PatternToken::Literal { ref content, .. }) => {
self.token_idx += 1;
return Ok(Some(InterpolatedKind::Literal(content)));
}
Some(&PatternToken::Placeholder(ref p)) => {
self.token_idx += 1;
self.current_replacement = self.replacements.take_replacement(p);
if self.current_replacement.is_none() {
return Err(InterpolatorError::MissingPlaceholder(p.clone()));
}
}
None => {
return Ok(None);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Parser, ParserOptions, Pattern, PatternError};
use std::convert::TryInto;
use std::{borrow::Cow, fmt::Display};
const SAMPLES: &[(&str, &[&[&str]], &str)] = &[
(
"'Foo' {0} 'and' {1}",
&[&["Hello"], &["World"]],
"Foo Hello and World",
),
(
"{0}, {1} 'and' {2}",
&[&["Start"], &["Middle"], &["End"]],
"Start, Middle and End",
),
("{0} 'at' {1}", &[&["Hello"], &["World"]], "Hello at World"),
];
#[derive(Debug, PartialEq)]
pub enum Element<'s> {
Literal(Cow<'s, str>),
}
impl Display for Element<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Literal(s) => f.write_str(s),
}
}
}
impl<'s> From<&'s str> for Element<'s> {
fn from(input: &'s str) -> Self {
Self::Literal(Cow::Borrowed(input))
}
}
#[test]
fn simple_interpolate() {
for sample in SAMPLES.iter() {
let pattern: Pattern<usize> = Parser::new(
sample.0,
ParserOptions {
allow_raw_letters: false,
},
)
.try_into()
.unwrap();
let replacements: Vec<Vec<Element>> = sample
.1
.iter()
.map(|r| r.iter().map(|&t| t.into()).collect())
.collect();
let interpolated_pattern = pattern
.interpolate::<'_, Element, _>(&replacements)
.unwrap();
let result = interpolated_pattern.to_string();
assert_eq!(result, sample.2);
}
}
#[test]
fn simple_interpolate_hash() {
let named_samples = vec![(
"{start}, {middle} 'and' {end}",
vec![
("start", vec!["Start"]),
("middle", vec!["Middle"]),
("end", vec!["End"]),
],
)];
for sample in &named_samples {
let pattern: Pattern<String> = Parser::new(
sample.0,
ParserOptions {
allow_raw_letters: false,
},
)
.try_into()
.unwrap();
let replacements: std::collections::HashMap<String, Vec<Element>> = sample
.1
.iter()
.map(|(k, v)| {
(
(*k).to_owned(),
v.iter().map(|&t| Element::from(t)).collect(),
)
})
.collect();
let interpolated_pattern = pattern
.interpolate::<'_, Element, _>(&replacements)
.unwrap();
let _ = interpolated_pattern.to_string();
}
}
#[test]
fn missing_placeholder() {
let samples: Vec<(&str, Vec<Element>)> = vec![("{0} days", vec![])];
for sample in &samples {
let pattern: Pattern<usize> = Parser::new(
sample.0,
ParserOptions {
allow_raw_letters: true,
},
)
.try_into()
.expect("Failed to parse a sample");
let interpolated_pattern = pattern.interpolate::<'_, Element, _>(&sample.1);
assert_eq!(
interpolated_pattern,
Err(PatternError::Interpolator(
InterpolatorError::MissingPlaceholder(0)
)),
);
}
}
}