1use crate::style::{Border, Color, Layout, Length, Style};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum Selector {
6 Type(String),
8 Class(String),
10 Id(String),
12}
13
14#[derive(Debug, Clone)]
16pub struct StyleRule {
17 pub selector: Selector,
19 pub style: Style,
21}
22
23#[derive(Debug, Clone, Default)]
28pub struct StyleSheet {
29 pub rules: Vec<StyleRule>,
32}
33
34impl StyleSheet {
35 pub fn parse(input: &str) -> Result<Self, String> {
44 let mut parser = Parser::new(input);
45 parser.parse()
46 }
47
48 pub fn resolve(&self, type_name: &str, id: Option<&str>, class: Option<&str>) -> Style {
54 let mut resolved = Style::default();
55
56 for rule in &self.rules {
58 match &rule.selector {
59 Selector::Type(name) if name == type_name => {
60 resolved = merge_styles(resolved, &rule.style);
61 }
62 Selector::Class(name) if class == Some(name) => {
63 resolved = merge_styles(resolved, &rule.style);
64 }
65 Selector::Id(name) if id == Some(name) => {
66 resolved = merge_styles(resolved, &rule.style);
67 }
68 _ => {}
69 }
70 }
71
72 resolved
73 }
74}
75
76pub fn inherit_style(parent: &Style, child: &Style) -> Style {
78 Style {
79 fg: child.fg.or(parent.fg),
80 bg: child.bg.or(parent.bg),
81 bold: child.bold || parent.bold,
82 italic: child.italic || parent.italic,
83 underline: child.underline || parent.underline,
84 width: if child.width != Length::Auto { child.width } else { parent.width },
85 height: if child.height != Length::Auto { child.height } else { parent.height },
86 padding: if child.padding != crate::geom::Insets::ZERO { child.padding } else { parent.padding },
87 margin: if child.margin != crate::geom::Insets::ZERO { child.margin } else { parent.margin },
88 layout: if child.layout != Layout::None { child.layout } else { parent.layout },
89 gap: if child.gap != 0 { child.gap } else { parent.gap },
90 flex_grow: if child.flex_grow != 0 { child.flex_grow } else { parent.flex_grow },
91 flex_shrink: child.flex_shrink && parent.flex_shrink,
92 border: if child.border != Border::None { child.border } else { parent.border },
93 }
94}
95
96pub fn merge_styles(base: Style, override_: &Style) -> Style {
98 Style {
99 fg: override_.fg.or(base.fg),
100 bg: override_.bg.or(base.bg),
101 bold: override_.bold || base.bold,
102 italic: override_.italic || base.italic,
103 underline: override_.underline || base.underline,
104 width: if override_.width != Length::Auto { override_.width } else { base.width },
105 height: if override_.height != Length::Auto { override_.height } else { base.height },
106 padding: if override_.padding != crate::geom::Insets::ZERO { override_.padding } else { base.padding },
107 margin: if override_.margin != crate::geom::Insets::ZERO { override_.margin } else { base.margin },
108 layout: if override_.layout != Layout::None { override_.layout } else { base.layout },
109 gap: if override_.gap != 0 { override_.gap } else { base.gap },
110 flex_grow: if override_.flex_grow != 0 { override_.flex_grow } else { base.flex_grow },
111 flex_shrink: override_.flex_shrink && base.flex_shrink,
112 border: if override_.border != Border::None { override_.border } else { base.border },
113 ..base
114 }
115}
116
117struct Parser<'a> {
118 chars: std::iter::Peekable<std::str::Chars<'a>>,
119 pos: usize,
120}
121
122impl<'a> Parser<'a> {
123 fn new(input: &'a str) -> Self {
124 Self {
125 chars: input.chars().peekable(),
126 pos: 0,
127 }
128 }
129
130 fn parse(&mut self) -> Result<StyleSheet, String> {
131 let mut rules = Vec::new();
132 self.skip_whitespace_and_comments();
133 while self.chars.peek().is_some() {
134 rules.push(self.parse_rule()?);
135 self.skip_whitespace_and_comments();
136 }
137 Ok(StyleSheet { rules })
138 }
139
140 fn parse_rule(&mut self) -> Result<StyleRule, String> {
141 let selector = self.parse_selector()?;
142 self.skip_whitespace();
143 self.expect('{')?;
144 let style = self.parse_declarations()?;
145 self.expect('}')?;
146 Ok(StyleRule { selector, style })
147 }
148
149 fn parse_selector(&mut self) -> Result<Selector, String> {
150 let next = self.peek_char().ok_or("expected selector")?;
151 match next {
152 '.' => {
153 self.advance();
154 let name = self.parse_ident()?;
155 Ok(Selector::Class(name))
156 }
157 '#' => {
158 self.advance();
159 let name = self.parse_ident()?;
160 Ok(Selector::Id(name))
161 }
162 c if c.is_alphabetic() || c == '_' || c == '-' => {
163 let name = self.parse_ident()?;
164 Ok(Selector::Type(name))
165 }
166 _ => Err(format!("unexpected char '{}' in selector", next)),
167 }
168 }
169
170 fn parse_declarations(&mut self) -> Result<Style, String> {
171 let mut style = Style::default();
172 loop {
173 self.skip_whitespace();
174 if self.peek_char() == Some('}') || self.peek_char().is_none() {
175 break;
176 }
177 let prop = self.parse_ident()?;
178 self.skip_whitespace();
179 self.expect(':')?;
180 self.skip_whitespace();
181 let value = self.parse_value(&prop)?;
182 self.apply_property(&mut style, &prop, &value);
183 self.skip_whitespace();
184 if self.peek_char() == Some(';') {
185 self.advance();
186 }
187 }
188 Ok(style)
189 }
190
191 fn parse_value(&mut self, _prop: &str) -> Result<String, String> {
192 let mut val = String::new();
193 while let Some(&c) = self.chars.peek() {
194 if c == ';' || c == '}' || c == '\n' {
195 break;
196 }
197 val.push(c);
198 self.advance();
199 }
200 Ok(val.trim().to_string())
201 }
202
203 fn apply_property(&self, style: &mut Style, prop: &str, value: &str) {
204 match prop {
205 "fg" | "color" => {
206 if let Some(c) = parse_color(value) {
207 style.fg = Some(c);
208 }
209 }
210 "bg" | "background" => {
211 if let Some(c) = parse_color(value) {
212 style.bg = Some(c);
213 }
214 }
215 "bold" => style.bold = value == "true",
216 "italic" => style.italic = value == "true",
217 "underline" => style.underline = value == "true",
218 "padding" => {
219 if let Ok(n) = value.parse::<u16>() {
220 style.padding = crate::geom::Insets::all(n);
221 }
222 }
223 "margin" => {
224 if let Ok(n) = value.parse::<u16>() {
225 style.margin = crate::geom::Insets::all(n);
226 }
227 }
228 "gap" => {
229 if let Ok(n) = value.parse::<u16>() {
230 style.gap = n;
231 }
232 }
233 "width" => style.width = parse_length(value),
234 "height" => style.height = parse_length(value),
235 "layout" => {
236 style.layout = match value {
237 "vertical" | "column" => Layout::Vertical,
238 "horizontal" | "row" => Layout::Horizontal,
239 _ => Layout::None,
240 }
241 }
242 "border" => {
243 style.border = match value {
244 "plain" => Border::Plain,
245 "rounded" => Border::Rounded,
246 "double" => Border::Double,
247 _ => Border::None,
248 }
249 }
250 _ => {} }
252 }
253
254 fn parse_ident(&mut self) -> Result<String, String> {
255 let mut ident = String::new();
256 while let Some(&c) = self.chars.peek() {
257 if c.is_alphanumeric() || c == '_' || c == '-' {
258 ident.push(c);
259 self.advance();
260 } else {
261 break;
262 }
263 }
264 if ident.is_empty() {
265 Err("expected identifier".into())
266 } else {
267 Ok(ident)
268 }
269 }
270
271 fn skip_whitespace(&mut self) {
272 while let Some(&c) = self.chars.peek() {
273 if c.is_whitespace() {
274 self.advance();
275 } else {
276 break;
277 }
278 }
279 }
280
281 fn skip_whitespace_and_comments(&mut self) {
282 loop {
283 self.skip_whitespace();
284 if self.peek_char() == Some('/') {
286 let mut iter = self.chars.clone();
288 iter.next();
289 if iter.next() == Some('/') {
290 while let Some(&c) = self.chars.peek() {
292 self.advance();
293 if c == '\n' {
294 break;
295 }
296 }
297 continue;
298 }
299 }
300 break;
301 }
302 }
303
304 fn peek_char(&mut self) -> Option<char> {
305 self.chars.peek().copied()
306 }
307
308 fn advance(&mut self) -> Option<char> {
309 self.pos += 1;
310 self.chars.next()
311 }
312
313 fn expect(&mut self, expected: char) -> Result<(), String> {
314 match self.chars.peek() {
315 Some(&c) if c == expected => {
316 self.advance();
317 Ok(())
318 }
319 Some(&c) => Err(format!("expected '{}', found '{}'", expected, c)),
320 None => Err(format!("expected '{}', found EOF", expected)),
321 }
322 }
323}
324
325fn parse_color(s: &str) -> Option<Color> {
326 match s {
327 "black" => Some(Color::Black),
328 "red" => Some(Color::Red),
329 "green" => Some(Color::Green),
330 "yellow" => Some(Color::Yellow),
331 "blue" => Some(Color::Blue),
332 "magenta" => Some(Color::Magenta),
333 "cyan" => Some(Color::Cyan),
334 "white" => Some(Color::White),
335 "gray" | "grey" => Some(Color::Gray),
336 _ => None,
337 }
338}
339
340fn parse_length(s: &str) -> Length {
341 if s == "auto" {
342 return Length::Auto;
343 }
344 if let Some(pct) = s.strip_suffix('%') {
345 if let Ok(n) = pct.parse::<u16>() {
346 return Length::Percent(n);
347 }
348 }
349 if let Some(frac) = s.strip_suffix("fr") {
350 if let Ok(n) = frac.parse::<u16>() {
351 return Length::Fraction(n);
352 }
353 }
354 if let Ok(n) = s.parse::<u16>() {
355 return Length::Fixed(n);
356 }
357 Length::Auto
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_parse_simple() {
366 let css = "Counter { fg: green; padding: 1; }";
367 let sheet = StyleSheet::parse(css).unwrap();
368 assert_eq!(sheet.rules.len(), 1);
369 assert_eq!(sheet.rules[0].selector, Selector::Type("Counter".into()));
370 assert_eq!(sheet.rules[0].style.fg, Some(Color::Green));
371 assert_eq!(sheet.rules[0].style.padding, crate::geom::Insets::all(1));
372 }
373
374 #[test]
375 fn test_parse_class_id() {
376 let css = ".card { border: rounded; } #header { bold: true; }";
377 let sheet = StyleSheet::parse(css).unwrap();
378 assert_eq!(sheet.rules.len(), 2);
379 assert_eq!(sheet.rules[0].selector, Selector::Class("card".into()));
380 assert_eq!(sheet.rules[1].selector, Selector::Id("header".into()));
381 }
382}