feat(runtime): add portable runtime support

req: push/009 req: push/010 req: push/011
This commit is contained in:
tmk241
2026-08-17 07:59:04 +02:00
parent e4bd6db13a
commit 353174604e
19 changed files with 1018 additions and 347 deletions
+5
View File
@@ -0,0 +1,5 @@
[package]
name = "hemplate-runtime"
version.workspace = true
edition.workspace = true
publish = false
+131
View File
@@ -0,0 +1,131 @@
use std::fmt;
pub mod error {
use std::fmt;
#[derive(Debug)]
pub struct HemplateError;
impl fmt::Display for HemplateError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("hemplate rendering failed")
}
}
impl std::error::Error for HemplateError {}
impl From<fmt::Error> for HemplateError {
fn from(_: fmt::Error) -> Self {
Self
}
}
}
pub trait Hemplate {
fn render_into(&self, buffer: &mut String) -> Result<(), error::HemplateError>;
fn render(&self) -> String {
let mut buffer = String::new();
self.render_into(&mut buffer)
.expect("writing a hemplate String cannot fail");
buffer
}
}
pub fn render<T: Hemplate + ?Sized>(value: &T) -> Result<String, error::HemplateError> {
let mut buffer = String::new();
value.render_into(&mut buffer)?;
Ok(buffer)
}
struct HtmlEscape<T: fmt::Display>(T);
impl<T: fmt::Display> fmt::Display for HtmlEscape<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for character in self.0.to_string().chars() {
match character {
'&' => formatter.write_str("&amp;")?,
'<' => formatter.write_str("&lt;")?,
'>' => formatter.write_str("&gt;")?,
'"' => formatter.write_str("&quot;")?,
'\'' => formatter.write_str("&#x27;")?,
other => fmt::Write::write_char(formatter, other)?,
}
}
Ok(())
}
}
pub mod render {
use super::{error::HemplateError, Hemplate, HtmlEscape};
use std::fmt;
pub struct Escaped<'a, T: ?Sized>(pub &'a T);
impl<T: Hemplate + ?Sized> Escaped<'_, T> {
pub fn render_to(&self, buffer: &mut String) -> Result<(), HemplateError> {
self.0.render_into(buffer)
}
}
pub trait EscapedFallback {
fn render_to(&self, buffer: &mut String) -> Result<(), HemplateError>;
}
impl<T: fmt::Display + ?Sized> EscapedFallback for Escaped<'_, T> {
fn render_to(&self, buffer: &mut String) -> Result<(), HemplateError> {
use fmt::Write;
write!(buffer, "{}", HtmlEscape(self.0))?;
Ok(())
}
}
pub struct Raw<'a, T: ?Sized>(pub &'a T);
impl<T: Hemplate + ?Sized> Raw<'_, T> {
pub fn render_to(&self, _buffer: &mut String) -> Result<(), HemplateError> {
panic!("hemplate: raw interpolation must not be used with Hemplate types")
}
}
pub trait RawFallback {
fn render_to(&self, buffer: &mut String) -> Result<(), HemplateError>;
}
impl<T: fmt::Display + ?Sized> RawFallback for Raw<'_, T> {
fn render_to(&self, buffer: &mut String) -> Result<(), HemplateError> {
use fmt::Write;
write!(buffer, "{}", self.0)?;
Ok(())
}
}
pub struct RawGuard<'a, T: ?Sized>(pub &'a T);
pub struct RawInterpolationNotAllowedForHemplateTypes;
impl<T: Hemplate + ?Sized> RawGuard<'_, T> {
pub fn check(&self) -> RawInterpolationNotAllowedForHemplateTypes {
RawInterpolationNotAllowedForHemplateTypes
}
}
pub trait RawGuardFallback {
fn check(&self);
}
impl<T: fmt::Display + ?Sized> RawGuardFallback for RawGuard<'_, T> {
fn check(&self) {}
}
impl<T: Hemplate + ?Sized> Hemplate for &T {
fn render_into(&self, buffer: &mut String) -> Result<(), HemplateError> {
(*self).render_into(buffer)
}
}
impl<T: Hemplate + ?Sized> Hemplate for Box<T> {
fn render_into(&self, buffer: &mut String) -> Result<(), HemplateError> {
(**self).render_into(buffer)
}
}
}