use std::{fmt, ops};
use anyhow::anyhow;
use crate::{write_escaped, Escapable, Result};
pub mod class;
pub use class::ClassAttr;
mod sealed {
pub use crate::sealed::EscapableSeal;
pub trait AttributeSeal {}
pub trait AttributesSeal {}
pub trait OptAttrValueSeal {}
}
fn write_attribute_name(
writer: &mut (impl fmt::Write + ?Sized),
name: &(impl fmt::Display + ?Sized),
) -> Result<()> {
use fmt::Write;
pub struct AttributeWriter<'a, W: fmt::Write + ?Sized> {
has_written: bool,
writer: &'a mut W,
}
impl<W: fmt::Write + ?Sized> fmt::Write for AttributeWriter<'_, W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
#[rustfmt::skip]
fn is_invalid_attribute_char(ch: char) -> bool {
matches!(
ch,
'\0'..='\x1F' | '\x7F'..='\u{9F}'
| ' ' | '"' | '\'' | '>' | '/' | '='
| '\u{FDD0}'..='\u{FDEF}'
| '\u{0FFFE}' | '\u{0FFFF}' | '\u{01FFFE}' | '\u{01FFFF}' | '\u{2FFFE}'
| '\u{2FFFF}' | '\u{3FFFE}' | '\u{03FFFF}' | '\u{04FFFE}' | '\u{4FFFF}'
| '\u{5FFFE}' | '\u{5FFFF}' | '\u{06FFFE}' | '\u{06FFFF}' | '\u{7FFFE}'
| '\u{7FFFF}' | '\u{8FFFE}' | '\u{08FFFF}' | '\u{09FFFE}' | '\u{9FFFF}'
| '\u{AFFFE}' | '\u{AFFFF}' | '\u{0BFFFE}' | '\u{0BFFFF}' | '\u{CFFFE}'
| '\u{CFFFF}' | '\u{DFFFE}' | '\u{0DFFFF}' | '\u{0EFFFE}' | '\u{EFFFF}'
| '\u{FFFFE}' | '\u{FFFFF}' | '\u{10FFFE}' | '\u{10FFFF}'
)
}
if s.contains(is_invalid_attribute_char) {
return Err(fmt::Error);
}
self.has_written |= !s.is_empty();
self.writer.write_str(s)
}
}
let mut attr_writer = AttributeWriter {
has_written: false,
writer,
};
write!(attr_writer, "{name}")?;
if !attr_writer.has_written {
return Err(anyhow!("attribute name cannot be empty"));
}
Ok(())
}
pub trait Attribute: sealed::AttributeSeal {
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()>;
}
impl<T: Attribute + ?Sized> sealed::AttributeSeal for &'_ T {}
impl<T: Attribute + ?Sized> Attribute for &'_ T {
#[inline]
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
T::render_into(self, writer)
}
}
impl<T: Attribute + ?Sized> sealed::AttributeSeal for &'_ mut T {}
impl<T: Attribute + ?Sized> Attribute for &'_ mut T {
#[inline]
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
T::render_into(self, writer)
}
}
impl sealed::AttributeSeal for String {}
impl Attribute for String {
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
writer.write_char(' ')?;
write_attribute_name(writer, self)?;
Ok(())
}
}
impl sealed::AttributeSeal for str {}
impl Attribute for str {
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
writer.write_char(' ')?;
write_attribute_name(writer, self)?;
Ok(())
}
}
impl<N: fmt::Display, T: fmt::Display> sealed::AttributeSeal for (N, T) {}
impl<N: fmt::Display, T: fmt::Display> Attribute for (N, T) {
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
writer.write_char(' ')?;
write_attribute_name(writer, &self.0)?;
writer.write_str("=\"")?;
write_escaped(writer, &self.1)?;
writer.write_char('"')?;
Ok(())
}
}
impl<N: fmt::Display> sealed::AttributeSeal for (N,) {}
impl<N: fmt::Display> Attribute for (N,) {
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
writer.write_char(' ')?;
write_attribute_name(writer, &self.0)?;
Ok(())
}
}
impl<A: Attribute> sealed::AttributeSeal for Option<A> {}
impl<A: Attribute> Attribute for Option<A> {
fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
match self {
Some(attr) => attr.render_into(writer),
None => Ok(()),
}
}
}
pub trait Attributes: sealed::AttributesSeal {
fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()>;
}
impl<T: Attribute> sealed::AttributesSeal for T {}
impl<T: Attribute> Attributes for T {
fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
Attribute::render_into(&self, writer)
}
}
impl sealed::AttributesSeal for () {}
impl Attributes for () {
#[inline]
fn render_into(self, _writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
Ok(())
}
}
impl<I: Attribute, T: IntoIterator<Item = I>> sealed::AttributesSeal for ops::RangeTo<T> {}
impl<I: Attribute, T: IntoIterator<Item = I>> Attributes for ops::RangeTo<T> {
fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
for attr in self.end {
attr.render_into(writer)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct PrerenderedAttrs {
raw: String,
}
impl PrerenderedAttrs {
#[inline]
pub fn new() -> Self {
Self { raw: String::new() }
}
#[inline]
pub fn from_raw_unchecked(raw: String) -> Self {
Self { raw }
}
#[inline]
pub fn add(&mut self, attr: &impl Attribute) -> Result<()> {
attr.render_into(&mut self.raw)
}
#[inline]
pub fn append(&mut self, attrs: impl Attributes) -> Result<()> {
attrs.render_into(&mut self.raw)
}
#[inline]
pub fn append_raw_unchecked(&mut self, raw: impl fmt::Display) -> fmt::Result {
use fmt::Write;
write!(&mut self.raw, "{raw}")
}
}
impl<A: Attributes> Extend<A> for PrerenderedAttrs {
fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
for attr in iter {
attr.render_into(&mut self.raw).unwrap();
}
}
}
impl sealed::AttributesSeal for &'_ PrerenderedAttrs {}
impl Attributes for &'_ PrerenderedAttrs {
#[inline]
fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
writer.write_str(&self.raw)?;
Ok(())
}
}
impl sealed::AttributesSeal for PrerenderedAttrs {}
impl Attributes for PrerenderedAttrs {
#[inline]
fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
(&self).render_into(writer)
}
}
pub trait OptAttrValue: sealed::OptAttrValueSeal {
fn to_opt_attr_value(self) -> Option<impl Escapable>;
}
impl sealed::OptAttrValueSeal for bool {}
impl OptAttrValue for bool {
fn to_opt_attr_value(self) -> Option<impl Escapable> {
struct Nothing;
impl sealed::EscapableSeal for Nothing {}
impl Escapable for Nothing {
const DONT_ESCAPE: bool = true;
#[inline]
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
match self {
true => Some(Nothing),
false => None,
}
}
}
impl<T: fmt::Display> sealed::OptAttrValueSeal for Option<T> {}
impl<T: fmt::Display> OptAttrValue for Option<T> {
fn to_opt_attr_value(self) -> Option<impl Escapable> {
struct Prequal<T: fmt::Display>(T);
impl<T: fmt::Display> sealed::EscapableSeal for Prequal<T> {}
impl<T: fmt::Display> Escapable for Prequal<T> {
const DONT_ESCAPE: bool = true;
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("=\"")?;
write_escaped(f, &self.0)?;
f.write_str("\"")?;
Ok(())
}
}
self.map(Prequal)
}
}