#![allow(clippy::doc_markdown)]
mod impls;
extern crate alloc;
use alloc::string::String;
use core::{
fmt::{self, Debug, Display, Formatter},
marker::PhantomData,
ptr,
};
use crate::{
Raw, Rendered, const_precise_live_drops_hack,
context::{AttributeValue, Context, Node},
};
#[derive(Clone, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct Buffer<C: Context = Node> {
inner: String,
context: PhantomData<C>,
}
pub type AttributeBuffer = Buffer<AttributeValue>;
#[expect(
clippy::missing_const_for_fn,
reason = "`Buffer` does not make sense in `const` contexts"
)]
impl Buffer {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::dangerously_from_string(String::new())
}
#[inline]
#[must_use]
pub fn dangerously_from_string(string: String) -> Self {
Self {
inner: string,
context: PhantomData,
}
}
#[inline]
#[must_use]
pub fn dangerously_from_string_mut(string: &mut String) -> &mut Self {
unsafe { &mut *ptr::from_mut(string).cast::<Self>() }
}
#[inline]
pub fn as_attribute_buffer(&mut self) -> &mut AttributeBuffer {
unsafe { &mut *ptr::from_mut(self).cast::<AttributeBuffer>() }
}
#[inline]
#[must_use]
pub fn rendered(self) -> Rendered<String> {
Rendered(self.inner)
}
}
#[expect(
clippy::missing_const_for_fn,
reason = "`Buffer` does not make sense in `const` contexts"
)]
impl<C: Context> Buffer<C> {
#[inline]
pub fn dangerously_get_string(&mut self) -> &mut String {
&mut self.inner
}
}
impl Debug for Buffer {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("Buffer").field(&self.inner).finish()
}
}
pub trait Renderable<C: Context = Node> {
fn render_to(&self, buffer: &mut Buffer<C>);
#[inline]
fn render(&self) -> Rendered<String>
where
Self: Renderable,
{
let mut buffer = Buffer::new();
self.render_to(&mut buffer);
buffer.rendered()
}
}
pub trait RenderableExt: Renderable {
#[inline]
fn memoize(&self) -> Raw<String> {
Raw::dangerously_create(self.render().into_inner())
}
}
impl<T: Renderable> RenderableExt for T {}
#[derive(Clone, Copy)]
#[must_use = "`Lazy` does nothing unless `.render()` or `.render_to()` is called"]
pub struct Lazy<F: Fn(&mut Buffer<C>), C: Context = Node> {
f: F,
context: PhantomData<C>,
}
pub type LazyAttribute<F> = Lazy<F, AttributeValue>;
impl<F: Fn(&mut Buffer<C>), C: Context> Lazy<F, C> {
#[inline]
pub const fn dangerously_create(f: F) -> Self {
Self {
f,
context: PhantomData,
}
}
#[inline]
pub const fn into_inner(self) -> F {
unsafe { const_precise_live_drops_hack!(self.f) }
}
#[inline]
pub const fn as_inner(&self) -> &F {
&self.f
}
}
impl<F: Fn(&mut Buffer<C>), C: Context> Renderable<C> for Lazy<F, C> {
#[inline]
fn render_to(&self, buffer: &mut Buffer<C>) {
(self.f)(buffer);
}
}
impl<F: Fn(&mut Buffer<C>), C: Context> Debug for Lazy<F, C> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("Lazy").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy)]
#[doc(alias = "%(...)")]
pub struct Displayed<T: Display>(pub T);
impl<C: Context, T: Display> Renderable<C> for Displayed<T>
where
for<'a> fmt::Arguments<'a>: Renderable<C>,
{
#[inline]
fn render_to(&self, buffer: &mut Buffer<C>) {
format_args!("{}", self.0).render_to(buffer);
}
}
#[derive(Debug, Clone, Copy)]
#[doc(alias = "?(...)")]
pub struct Debugged<T: Debug>(pub T);
impl<C: Context, T: Debug> Renderable<C> for Debugged<T>
where
for<'a> fmt::Arguments<'a>: Renderable<C>,
{
#[inline]
fn render_to(&self, buffer: &mut Buffer<C>) {
format_args!("{:?}", self.0).render_to(buffer);
}
}