use core::pin::Pin;
use crate::VariableMap;
use crate::error::{ExpandError, ParseError};
use crate::non_aliasing::NonAliasing;
mod raw;
#[derive(Clone)]
pub struct Template<'a> {
source: &'a str,
raw: raw::Template,
}
impl std::fmt::Debug for Template<'_> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Template").field(&self.source).finish()
}
}
impl<'a> Template<'a> {
#[inline]
#[allow(clippy::should_implement_trait)]
pub fn from_str(source: &'a str) -> Result<Self, ParseError> {
Ok(Self {
source,
raw: raw::Template::parse(source.as_bytes(), 0)?,
})
}
#[inline]
pub fn source(&self) -> &str {
self.source
}
pub fn expand<'b, M>(&self, variables: &'b M) -> Result<String, ExpandError>
where
M: VariableMap<'b> + ?Sized,
M::Value: AsRef<str>,
{
let mut output = Vec::with_capacity(self.source.len() + self.source.len() / 10);
self.raw.expand(&mut output, self.source.as_bytes(), variables, &|x| {
x.as_ref().as_bytes()
})?;
unsafe { Ok(String::from_utf8_unchecked(output)) }
}
unsafe fn transmute_lifetime<'b>(self) -> Template<'b> {
std::mem::transmute(self)
}
}
pub struct TemplateBuf {
template: NonAliasing<Template<'static>>,
source: Pin<String>,
}
impl Clone for TemplateBuf {
fn clone(&self) -> Self {
let source = self.source.clone();
let raw = self.template.inner().raw.clone();
let template = Template {
raw,
source: &*source,
};
let template = unsafe { template.transmute_lifetime() };
let template = NonAliasing::new(template);
Self {
template,
source,
}
}
}
impl std::fmt::Debug for TemplateBuf {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TemplateBuf")
.field(&self.template.inner().source())
.finish()
}
}
impl TemplateBuf {
#[inline]
pub fn from_string(source: String) -> Result<Self, ParseError> {
let source = Pin::new(source);
let template = Template::from_str(&*source)?;
let template = unsafe { template.transmute_lifetime() };
let template = NonAliasing::new(template);
Ok(Self { source, template })
}
#[inline]
pub fn into_source(self) -> String {
drop(self.template);
Pin::into_inner(self.source)
}
#[inline]
#[allow(clippy::needless_lifetimes)]
pub fn as_template<'a>(&'a self) -> &'a Template<'a> {
self.template.inner()
}
pub fn expand<'b, M>(&self, variables: &'b M) -> Result<String, ExpandError>
where
M: VariableMap<'b> + ?Sized,
M::Value: AsRef<str>,
{
self.as_template().expand(variables)
}
}
impl<'a> From<&'a TemplateBuf> for &'a Template<'a> {
#[inline]
fn from(other: &'a TemplateBuf) -> Self {
other.as_template()
}
}
impl<'a> From<&'a TemplateBuf> for Template<'a> {
#[inline]
fn from(other: &'a TemplateBuf) -> Self {
other.as_template().clone()
}
}
impl From<&Template<'_>> for TemplateBuf {
#[inline]
fn from(other: &Template<'_>) -> Self {
other.clone().into()
}
}
impl From<Template<'_>> for TemplateBuf {
#[inline]
fn from(other: Template<'_>) -> Self {
let source: Pin<String> = Pin::new(other.source.into());
let template = Template {
source: &*source,
raw: other.raw,
};
let template = unsafe { template.transmute_lifetime() };
let template = NonAliasing::new(template);
Self { source, template }
}
}
#[derive(Clone)]
pub struct ByteTemplate<'a> {
source: &'a [u8],
raw: raw::Template,
}
impl std::fmt::Debug for ByteTemplate<'_> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ByteTemplate")
.field(&DebugByteString(self.source))
.finish()
}
}
impl<'a> ByteTemplate<'a> {
#[inline]
pub fn from_slice(source: &'a [u8]) -> Result<Self, ParseError> {
Ok(Self {
source,
raw: raw::Template::parse(source, 0)?,
})
}
#[inline]
pub fn source(&self) -> &[u8] {
self.source
}
pub fn expand<'b, M>(&self, variables: &'b M) -> Result<Vec<u8>, ExpandError>
where
M: VariableMap<'b> + ?Sized,
M::Value: AsRef<[u8]>,
{
let mut output = Vec::with_capacity(self.source.len() + self.source.len() / 10);
self.raw.expand(&mut output, self.source, variables, &|x| x.as_ref())?;
Ok(output)
}
unsafe fn transmute_lifetime<'b>(self) -> ByteTemplate<'b> {
std::mem::transmute(self)
}
}
pub struct ByteTemplateBuf {
template: NonAliasing<ByteTemplate<'static>>,
source: Pin<Vec<u8>>,
}
impl Clone for ByteTemplateBuf {
fn clone(&self) -> Self {
let source = self.source.clone();
let raw = self.template.inner().raw.clone();
let template = ByteTemplate {
raw,
source: &*source,
};
let template = unsafe { template.transmute_lifetime() };
let template = NonAliasing::new(template);
Self {
template,
source,
}
}
}
impl std::fmt::Debug for ByteTemplateBuf {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ByteTemplateBuf")
.field(&DebugByteString(self.as_template().source()))
.finish()
}
}
impl ByteTemplateBuf {
#[inline]
pub fn from_vec(source: Vec<u8>) -> Result<Self, ParseError> {
let source = Pin::new(source);
let template = ByteTemplate::from_slice(&*source)?;
let template = unsafe { template.transmute_lifetime() };
let template = NonAliasing::new(template);
Ok(Self { source, template })
}
#[inline]
pub fn into_source(self) -> Vec<u8> {
drop(self.template);
Pin::into_inner(self.source)
}
#[inline]
#[allow(clippy::needless_lifetimes)]
pub fn as_template<'a>(&'a self) -> &'a ByteTemplate<'a> {
self.template.inner()
}
pub fn expand<'b, M>(&self, variables: &'b M) -> Result<Vec<u8>, ExpandError>
where
M: VariableMap<'b> + ?Sized,
M::Value: AsRef<[u8]>,
{
self.as_template().expand(variables)
}
}
impl<'a> From<&'a ByteTemplateBuf> for &'a ByteTemplate<'a> {
#[inline]
fn from(other: &'a ByteTemplateBuf) -> Self {
other.as_template()
}
}
impl<'a> From<&'a ByteTemplateBuf> for ByteTemplate<'a> {
#[inline]
fn from(other: &'a ByteTemplateBuf) -> Self {
other.as_template().clone()
}
}
impl From<&ByteTemplate<'_>> for ByteTemplateBuf {
#[inline]
fn from(other: &ByteTemplate<'_>) -> Self {
other.clone().into()
}
}
impl From<ByteTemplate<'_>> for ByteTemplateBuf {
#[inline]
fn from(other: ByteTemplate<'_>) -> Self {
let source: Vec<u8> = other.source.into();
let source = Pin::new(source);
let template = ByteTemplate {
source: &*source,
raw: other.raw,
};
let template = unsafe { template.transmute_lifetime() };
let template = NonAliasing::new(template);
Self { source, template }
}
}
struct DebugByteString<'a>(&'a [u8]);
impl std::fmt::Debug for DebugByteString<'_> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Ok(data) = std::str::from_utf8(self.0) {
write!(f, "b{:?}", data)
} else {
std::fmt::Debug::fmt(self.0, f)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert2::{assert, check, let_assert};
use std::collections::BTreeMap;
#[test]
fn test_clone_template_buf() {
let mut map: BTreeMap<String, String> = BTreeMap::new();
map.insert("name".into(), "world".into());
let source = "Hello ${name}!";
let_assert!(Ok(buf1) = TemplateBuf::from_string(source.into()));
let buf2 = buf1.clone();
let mut string = buf1.into_source();
string.as_mut()[..5].make_ascii_uppercase();
check!(let Ok("Hello world!") = buf2.expand(&map).as_deref());
assert!(buf2.as_template().source() == source);
assert!(buf2.into_source() == source);
}
#[test]
fn test_clone_byte_template_buf() {
let mut map: BTreeMap<String, String> = BTreeMap::new();
map.insert("name".into(), "world".into());
let source = b"Hello ${name}!";
let_assert!(Ok(buf1) = ByteTemplateBuf::from_vec(source.into()));
let buf2 = buf1.clone();
let mut string = buf1.into_source();
string.as_mut_slice()[..5].make_ascii_uppercase();
check!(let Ok(b"Hello world!") = buf2.expand(&map).as_deref());
assert!(buf2.as_template().source() == source);
assert!(buf2.into_source() == source);
}
#[test]
fn test_move_template_buf() {
#[inline(never)]
fn check_template(buf: TemplateBuf) {
let mut map: BTreeMap<String, String> = BTreeMap::new();
map.insert("name".into(), "world".into());
assert!(buf.as_template().source() == "Hello ${name}!");
let_assert!(Ok(expanded) = buf.as_template().expand(&map));
assert!(expanded == "Hello world!");
}
let source = "Hello ${name}!";
let_assert!(Ok(buf1) = TemplateBuf::from_string(source.into()));
check_template(buf1);
}
#[test]
fn test_move_byte_template_buf() {
#[inline(never)]
fn check_template(buf: ByteTemplateBuf) {
let mut map: BTreeMap<String, String> = BTreeMap::new();
map.insert("name".into(), "world".into());
assert!(buf.as_template().source() == b"Hello ${name}!");
let_assert!(Ok(expanded) = buf.as_template().expand(&map));
assert!(expanded == b"Hello world!");
}
let source = b"Hello ${name}!";
let_assert!(Ok(buf1) = ByteTemplateBuf::from_vec(source.into()));
check_template(buf1);
}
}