css_ast/properties/
mod.rs

1use crate::values;
2use css_parse::{
3	Build, ComponentValues, Cursor, DeclarationValue, KindSet, Parser, Peek, Result as ParserResult, State, T,
4	keyword_set,
5};
6use csskit_derives::{Parse, ToCursors, ToSpan, Visitable};
7use std::{fmt::Debug, hash::Hash};
8
9// The build.rs generates a list of CSS properties from the value mods
10include!(concat!(env!("OUT_DIR"), "/css_apply_properties.rs"));
11
12#[derive(Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
14#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
15pub struct Custom<'a>(pub ComponentValues<'a>);
16
17#[derive(Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
19#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
20pub struct Computed<'a>(pub ComponentValues<'a>);
21
22impl<'a> Peek<'a> for Computed<'a> {
23	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
24		<T![Function]>::peek(p, c)
25			&& matches!(
26				p.parse_str_lower(c),
27				"var"
28					| "calc" | "min"
29					| "max" | "clamp"
30					| "round" | "mod"
31					| "rem" | "sin" | "cos"
32					| "tan" | "asin"
33					| "atan" | "atan2"
34					| "pow" | "sqrt"
35					| "hypot" | "log"
36					| "exp" | "abs" | "sign"
37			)
38	}
39}
40
41#[derive(Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
43#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
44pub struct Unknown<'a>(pub ComponentValues<'a>);
45
46macro_rules! style_value {
47	( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
48		#[derive(ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
49		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(tag = "type", rename_all = "kebab-case"))]
50		#[visit]
51		pub enum StyleValue<'a> {
52			#[visit(skip)]
53			Initial(T![Ident]),
54			#[visit(skip)]
55			Inherit(T![Ident]),
56			#[visit(skip)]
57			Unset(T![Ident]),
58			#[visit(skip)]
59			Revert(T![Ident]),
60			#[visit(skip)]
61			RevertLayer(T![Ident]),
62			#[cfg_attr(feature = "serde", serde(untagged))]
63			Custom(Custom<'a>),
64			#[cfg_attr(feature = "serde", serde(untagged))]
65			Computed(Computed<'a>),
66			#[cfg_attr(feature = "serde", serde(untagged))]
67			Unknown(Unknown<'a>),
68			$(
69				#[cfg_attr(feature = "serde", serde(untagged))]
70				$name(values::$ty$(<$a>)?),
71			)+
72		}
73	}
74}
75
76apply_properties!(style_value);
77
78keyword_set!(pub enum CSSWideKeyword {
79	Initial: "initial",
80	Inherit: "inherit",
81	Unset: "unset",
82	Revert: "revert",
83	RevertLayer: "revert-layer",
84});
85
86macro_rules! define_property_id {
87	( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
88		keyword_set!(pub enum PropertyId {
89			$($name: $str,)+
90		});
91	}
92}
93apply_properties!(define_property_id);
94
95impl<'a> DeclarationValue<'a> for StyleValue<'a> {
96	type ComputedValue = Computed<'a>;
97
98	fn valid_declaration_name(p: &Parser<'a>, c: Cursor) -> bool {
99		PropertyId::peek(p, c)
100	}
101
102	fn is_unknown(&self) -> bool {
103		matches!(self, Self::Unknown(_))
104	}
105
106	fn is_initial(&self) -> bool {
107		matches!(self, Self::Initial(_))
108	}
109
110	fn is_inherit(&self) -> bool {
111		matches!(self, Self::Inherit(_))
112	}
113
114	fn is_unset(&self) -> bool {
115		matches!(self, Self::Unset(_))
116	}
117
118	fn is_revert(&self) -> bool {
119		matches!(self, Self::Revert(_))
120	}
121
122	fn is_revert_layer(&self) -> bool {
123		matches!(self, Self::RevertLayer(_))
124	}
125
126	fn needs_computing(&self) -> bool {
127		matches!(self, Self::Computed(_))
128	}
129
130	fn parse_custom_declaration_value(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
131		p.parse::<Custom>().map(Self::Custom)
132	}
133
134	fn parse_computed_declaration_value(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
135		p.parse::<Computed>().map(Self::Computed)
136	}
137
138	fn parse_specified_declaration_value(p: &mut Parser<'a>, name: Cursor) -> ParserResult<Self> {
139		match p.parse_if_peek::<CSSWideKeyword>()? {
140			Some(CSSWideKeyword::Initial(ident)) => return Ok(Self::Initial(ident)),
141			Some(CSSWideKeyword::Inherit(ident)) => return Ok(Self::Inherit(ident)),
142			Some(CSSWideKeyword::Unset(ident)) => return Ok(Self::Unset(ident)),
143			Some(CSSWideKeyword::Revert(ident)) => return Ok(Self::Revert(ident)),
144			Some(CSSWideKeyword::RevertLayer(ident)) => return Ok(Self::RevertLayer(ident)),
145			None => {}
146		}
147		macro_rules! parse_declaration_value {
148			( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
149				match PropertyId::build(p, name) {
150					$(PropertyId::$name(_) => p.parse::<values::$ty>().map(Self::$name),)+
151				}
152			}
153		}
154		apply_properties!(parse_declaration_value)
155	}
156
157	fn parse_unknown_declaration_value(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
158		p.parse::<Unknown>().map(Self::Unknown)
159	}
160}
161
162#[cfg(test)]
163mod tests {
164	use super::*;
165	use css_parse::{Declaration, assert_parse};
166
167	type Property<'a> = Declaration<'a, StyleValue<'a>>;
168
169	#[test]
170	fn size_test() {
171		assert_eq!(std::mem::size_of::<Property>(), 368);
172		assert_eq!(std::mem::size_of::<StyleValue>(), 296);
173	}
174
175	#[test]
176	fn test_writes() {
177		assert_parse!(Property, "width:inherit", Property { value: StyleValue::Inherit(_), .. });
178		assert_parse!(
179			Property,
180			"width:inherit!important",
181			Property { value: StyleValue::Inherit(_), important: Some(_), .. }
182		);
183		assert_parse!(Property, "width:revert;", Property { value: StyleValue::Revert(_), semicolon: Some(_), .. });
184		assert_parse!(Property, "width:var(--a)", Property { value: StyleValue::Computed(_), .. });
185
186		assert_parse!(Property, "float:none!important");
187		assert_parse!(Property, "width:1px");
188		assert_parse!(Property, "width:min(1px, 2px)");
189		assert_parse!(Property, "border:1px solid var(--red)");
190		// Should still parse unknown properties
191		assert_parse!(Property, "dunno:like whatever");
192		assert_parse!(Property, "rotate:1.21gw");
193		assert_parse!(Property, "_background:black");
194		assert_parse!(Property, "--custom:{foo:{bar};baz:(bing);}");
195	}
196}