1use noxid_css_ir::{Keyframe, StyleAtRuleBlock, StyleDeclaration, StyleProgram, StyleRule};
2
3pub fn generate(program: &StyleProgram) -> String {
4 let mut output = String::new();
5 for sheet in &program.sheets {
6 for rule in &sheet.rules {
7 emit_rule(&mut output, rule, 0);
8 }
9 }
10 output
11}
12
13fn emit_rule(output: &mut String, rule: &StyleRule, depth: usize) {
14 let indent = " ".repeat(depth);
15 match rule {
16 StyleRule::Qualified {
17 scoped_selector,
18 declarations,
19 ..
20 } => {
21 output.push_str(&format!("{indent}{scoped_selector} {{\n"));
22 emit_declarations(output, declarations, depth + 1);
23 output.push_str(&format!("{indent}}}\n"));
24 }
25 StyleRule::AtRule {
26 name,
27 prelude,
28 block,
29 ..
30 } => {
31 let separator = if prelude.is_empty() { "" } else { " " };
32 match block {
33 None => output.push_str(&format!("{indent}@{name}{separator}{prelude};\n")),
34 Some(block) => {
35 output.push_str(&format!("{indent}@{name}{separator}{prelude} {{\n"));
36 match block {
37 StyleAtRuleBlock::Rules(rules) => {
38 for rule in rules {
39 emit_rule(output, rule, depth + 1);
40 }
41 }
42 StyleAtRuleBlock::Declarations(declarations) => {
43 emit_declarations(output, declarations, depth + 1);
44 }
45 }
46 output.push_str(&format!("{indent}}}\n"));
47 }
48 }
49 }
50 StyleRule::Keyframes {
51 scoped_name,
52 vendor_prefix,
53 frames,
54 ..
55 } => {
56 output.push_str(&format!(
57 "{indent}@{}keyframes {scoped_name} {{\n",
58 vendor_prefix.as_deref().unwrap_or("")
59 ));
60 for frame in frames {
61 emit_keyframe(output, frame, depth + 1);
62 }
63 output.push_str(&format!("{indent}}}\n"));
64 }
65 }
66}
67
68fn emit_keyframe(output: &mut String, frame: &Keyframe, depth: usize) {
69 let indent = " ".repeat(depth);
70 output.push_str(&format!("{indent}{} {{\n", frame.selector));
71 emit_declarations(output, &frame.declarations, depth + 1);
72 output.push_str(&format!("{indent}}}\n"));
73}
74
75fn emit_declarations(output: &mut String, declarations: &[StyleDeclaration], depth: usize) {
76 let indent = " ".repeat(depth);
77 for declaration in declarations {
78 let important = if declaration.important {
79 " !important"
80 } else {
81 ""
82 };
83 output.push_str(&format!(
84 "{indent}{}: {}{important};\n",
85 declaration.name, declaration.value
86 ));
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use noxid_css_ir::{StyleRule, StyleSheet};
94 use noxid_ir::SemanticId;
95 use noxid_source::Span;
96
97 #[test]
98 fn emits_scoped_rules_and_at_rules() {
99 let program = StyleProgram {
100 sheets: vec![StyleSheet {
101 id: SemanticId::style("Panel"),
102 component: SemanticId::component("Panel"),
103 component_name: "Panel".into(),
104 scope: "noxid-test".into(),
105 cst_token_count: 4,
106 span: Span::new(0, 10),
107 rules: vec![StyleRule::Qualified {
108 id: SemanticId::css_rule("Panel", 1),
109 selector: ".panel".into(),
110 scoped_selector: ".panel[data-noxid-scope=\"noxid-test\"]".into(),
111 declarations: vec![StyleDeclaration {
112 id: SemanticId::css_declaration("Panel", 1, 1),
113 name: "color".into(),
114 value: "red".into(),
115 important: false,
116 span: Span::new(0, 10),
117 }],
118 span: Span::new(0, 10),
119 }],
120 }],
121 };
122 assert_eq!(
123 generate(&program),
124 ".panel[data-noxid-scope=\"noxid-test\"] {\n color: red;\n}\n"
125 );
126 }
127}