#![forbid(unsafe_code)]
extern crate self as pliego_css;
use core::fmt;
#[doc(hidden)]
pub mod __private {
pub use pliego_css_macros::{pc_id, pcx_id};
#[must_use]
pub const fn style_from_compiled_id(id: u128) -> super::Style {
super::Style {
id: super::StyleId(pliego_css_ir::StyleId::new(id)),
}
}
}
#[macro_export]
macro_rules! pc {
($($tokens:tt)*) => {
$crate::__private::style_from_compiled_id($crate::__private::pc_id!($($tokens)*))
};
}
#[macro_export]
macro_rules! pcx {
($($tokens:tt)*) => {
$crate::__private::style_from_compiled_id($crate::__private::pcx_id!($($tokens)*))
};
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct StyleId(pliego_css_ir::StyleId);
impl StyleId {
#[must_use]
pub const fn get(self) -> u128 {
self.0.get()
}
#[must_use]
pub const fn is_unresolved(self) -> bool {
self.0.is_unresolved()
}
#[must_use]
pub fn to_class_name(self) -> String {
self.0.to_class_name()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct Style {
id: StyleId,
}
impl Style {
pub const EMPTY: Self = Self {
id: StyleId(pliego_css_ir::StyleId::UNRESOLVED),
};
#[must_use]
pub const fn id(self) -> StyleId {
self.id
}
#[must_use]
pub fn class_name(self) -> String {
if self.is_empty() {
String::new()
} else {
self.id.to_class_name()
}
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.id.is_unresolved()
}
}
impl fmt::Display for Style {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
Ok(())
} else {
formatter.write_str(&self.id.to_class_name())
}
}
}
impl From<Style> for String {
fn from(style: Style) -> Self {
style.class_name()
}
}
#[cfg(test)]
mod tests {
use super::*;
const REUSABLE_CARD: Style = pc!("rounded-lg bg-surface p-6");
#[test]
fn macro_returns_validated_static_style() {
let style = pc!("flex items-center gap-4");
assert!(!style.id().is_unresolved());
assert!(style.class_name().starts_with("pc_"));
assert!(!style.is_empty());
}
#[test]
fn empty_style_is_explicit() {
assert!(Style::EMPTY.is_empty());
assert!(Style::EMPTY.id().is_unresolved());
assert_eq!(Style::EMPTY.id().to_class_name(), "pc_0");
assert!(Style::EMPTY.class_name().is_empty());
assert_eq!(Style::EMPTY.to_string(), "");
}
#[test]
fn style_formats_as_its_css_class() {
let style = pc!("flex gap-4");
assert_eq!(style.to_string(), style.class_name());
assert_eq!(String::from(style), style.class_name());
}
#[test]
fn runtime_handle_is_only_the_style_identity() {
assert_eq!(
core::mem::size_of::<StyleId>(),
core::mem::size_of::<u128>()
);
assert_eq!(
core::mem::size_of::<Style>(),
core::mem::size_of::<StyleId>()
);
}
#[test]
fn validated_styles_are_reusable_constants() {
assert_eq!(REUSABLE_CARD.id(), pc!("p-6 bg-surface rounded-lg").id());
}
#[test]
fn equivalent_utility_order_has_one_identity() {
assert_eq!(pc!("flex gap-4").id(), pc!("gap-4 flex").id());
}
#[test]
fn conditional_if_returns_a_complete_precompiled_style() {
let selected = true;
let style = pcx!(
"inline-flex rounded-md bg-surface",
if selected {
"bg-accent text-white"
} else {
"text-ink"
},
);
assert_eq!(
style.id(),
pc!("inline-flex rounded-md bg-accent text-white").id()
);
}
#[test]
fn conditional_match_compiles_every_literal_arm() {
enum Variant {
Primary,
Ghost,
}
let variant = Variant::Ghost;
let style = pcx!(
"rounded-md",
match variant {
Variant::Primary => "bg-accent text-white",
Variant::Ghost => "bg-transparent text-accent",
},
);
assert_eq!(
style.id(),
pc!("rounded-md bg-transparent text-accent").id()
);
let _ = Variant::Primary;
}
#[test]
fn independent_clauses_select_one_precompiled_cartesian_result() {
let active = false;
let compact = true;
let style = pcx!(
"inline-flex rounded-md",
if active { "opacity-100" } else { "opacity-50" },
if compact { "text-sm" } else { "text-lg" },
);
assert_eq!(
style.id(),
pc!("inline-flex rounded-md opacity-50 text-sm").id()
);
}
#[test]
fn each_independent_clause_expression_is_evaluated_once() {
use core::cell::Cell;
let evaluations = Cell::new(0_u8);
let condition = || {
evaluations.set(evaluations.get() + 1);
true
};
let style = pcx!(
"block",
if condition() {
"opacity-100"
} else {
"opacity-50"
},
if condition() { "text-sm" } else { "text-lg" },
);
assert_eq!(evaluations.get(), 2);
assert_eq!(style.id(), pc!("block opacity-100 text-sm").id());
}
#[test]
fn independent_if_and_match_preserve_rust_selection_semantics() {
enum Size {
Small,
Large,
}
let active = true;
let size = Size::Large;
let style = pcx!(
"inline-flex",
if active { "opacity-100" } else { "opacity-50" },
match size {
Size::Small => "text-sm",
Size::Large => "text-lg",
},
);
assert_eq!(style.id(), pc!("inline-flex opacity-100 text-lg").id());
let _ = Size::Small;
}
}