use std::{fmt, io, marker::PhantomData};
pub use templr_macros::Template;
use crate::Result;
struct _DynTemplate(dyn Template);
pub trait ToTemplate<Ctx: ?Sized = ()> {
fn to_template(&self) -> impl Template<Ctx> + '_;
}
impl<Ctx: ?Sized, T: ToTemplate<Ctx> + ?Sized> ToTemplate<Ctx> for &'_ T {
fn to_template(&self) -> impl Template<Ctx> + '_ {
T::to_template(self)
}
}
pub trait Template<Ctx: ?Sized = ()> {
fn size_hint(&self) -> usize;
fn render_with_children_into(
&self,
writer: &mut dyn fmt::Write,
ctx: &Ctx,
children: &dyn Template<Ctx>,
) -> Result<()>;
fn render_into(&self, writer: &mut dyn fmt::Write, ctx: &Ctx) -> Result<()> {
self.render_with_children_into(writer, ctx, &())
}
fn render(&self, ctx: &Ctx) -> Result<String> {
let mut buf = String::new();
let _ = buf.try_reserve(self.size_hint());
self.render_into(&mut buf, ctx)?;
Ok(buf)
}
fn write_into(&self, writer: &mut dyn io::Write, ctx: &Ctx) -> io::Result<()> {
struct Adapter<'a, T: ?Sized + 'a> {
inner: &'a mut T,
error: io::Result<()>,
}
impl<T: io::Write + ?Sized> fmt::Write for Adapter<'_, T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
match self.inner.write_all(s.as_bytes()) {
Ok(()) => Ok(()),
Err(e) => {
self.error = Err(e);
Err(fmt::Error)
}
}
}
}
let mut output = Adapter {
inner: writer,
error: Ok(()),
};
match self.render_into(&mut output, ctx) {
Ok(()) => Ok(()),
Err(err) => {
if output.error.is_err() {
output.error
} else {
Err(io::Error::new(io::ErrorKind::Other, err))
}
}
}
}
}
impl<Ctx: ?Sized> Template<Ctx> for () {
fn size_hint(&self) -> usize {
0
}
fn render_with_children_into(
&self,
_writer: &mut dyn fmt::Write,
_ctx: &Ctx,
_children: &dyn Template<Ctx>,
) -> Result<()> {
Ok(())
}
fn render_into(&self, _writer: &mut dyn fmt::Write, _ctx: &Ctx) -> Result<()> {
Ok(())
}
fn render(&self, _ctx: &Ctx) -> Result<String> {
Ok(String::new())
}
fn write_into(&self, _writer: &mut dyn io::Write, _ctx: &Ctx) -> io::Result<()> {
Ok(())
}
}
impl<Ctx: ?Sized, T: Template<Ctx> + ?Sized> Template<Ctx> for &'_ T {
fn size_hint(&self) -> usize {
T::size_hint(self)
}
fn render_with_children_into(
&self,
writer: &mut dyn fmt::Write,
ctx: &Ctx,
children: &dyn Template<Ctx>,
) -> Result<()> {
T::render_with_children_into(self, writer, ctx, children)
}
fn render_into(&self, writer: &mut dyn fmt::Write, ctx: &Ctx) -> Result<()> {
T::render_into(self, writer, ctx)
}
fn render(&self, ctx: &Ctx) -> Result<String> {
T::render(self, ctx)
}
fn write_into(&self, writer: &mut dyn io::Write, ctx: &Ctx) -> io::Result<()> {
T::write_into(self, writer, ctx)
}
}
#[derive(Debug)]
pub struct FnTemplate<F, Ctx: ?Sized = ()>
where
F: Fn(&mut dyn fmt::Write, &Ctx, &dyn Template<Ctx>) -> Result<()>,
{
size_hint: usize,
render_with_children_into: F,
_phantom: PhantomData<Ctx>,
}
impl<F, Ctx: ?Sized> FnTemplate<F, Ctx>
where
F: Fn(&mut dyn fmt::Write, &Ctx, &dyn Template<Ctx>) -> Result<()>,
{
#[inline]
pub fn new(render_with_children_into: F) -> Self {
Self {
size_hint: 80,
render_with_children_into,
_phantom: PhantomData,
}
}
#[inline]
pub fn new_sized(size_hint: usize, render_with_children_into: F) -> Self {
Self {
size_hint,
render_with_children_into,
_phantom: PhantomData,
}
}
}
impl<F, Ctx: ?Sized> Template<Ctx> for FnTemplate<F, Ctx>
where
F: Fn(&mut dyn fmt::Write, &Ctx, &dyn Template<Ctx>) -> Result<()>,
{
fn size_hint(&self) -> usize {
self.size_hint
}
fn render_with_children_into(
&self,
writer: &mut dyn fmt::Write,
ctx: &Ctx,
children: &dyn Template<Ctx>,
) -> Result<()> {
(self.render_with_children_into)(writer, ctx, children)
}
}
impl<F> fmt::Display for FnTemplate<F>
where
F: Fn(&mut dyn fmt::Write, &(), &dyn Template<()>) -> crate::Result<()> + Send,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.render_into(f, &()).map_err(|_| fmt::Error)
}
}
#[macro_export]
macro_rules! templ_ret {
($lt:lifetime, $ctx:ty $(,)?) => {
$crate::FnTemplate<
impl $lt + Fn(
&mut dyn ::std::fmt::Write,
&$ctx,
&dyn $crate::Template<$ctx>,
) -> $crate::Result<()>,
$ctx,
>
};
($lt:lifetime $(,)?) => { $crate::templ_ret![$lt, ()] };
($ctx:ty $(,)?) => { $crate::templ_ret!['static, $ctx] };
() => { $crate::templ_ret!['static, ()] };
}