use noxid_css_ir::{Keyframe, StyleAtRuleBlock, StyleDeclaration, StyleProgram, StyleRule};
pub fn generate(program: &StyleProgram) -> String {
let mut output = String::new();
for sheet in &program.sheets {
for rule in &sheet.rules {
emit_rule(&mut output, rule, 0);
}
}
output
}
fn emit_rule(output: &mut String, rule: &StyleRule, depth: usize) {
let indent = " ".repeat(depth);
match rule {
StyleRule::Qualified {
scoped_selector,
declarations,
..
} => {
output.push_str(&format!("{indent}{scoped_selector} {{\n"));
emit_declarations(output, declarations, depth + 1);
output.push_str(&format!("{indent}}}\n"));
}
StyleRule::AtRule {
name,
prelude,
block,
..
} => {
let separator = if prelude.is_empty() { "" } else { " " };
match block {
None => output.push_str(&format!("{indent}@{name}{separator}{prelude};\n")),
Some(block) => {
output.push_str(&format!("{indent}@{name}{separator}{prelude} {{\n"));
match block {
StyleAtRuleBlock::Rules(rules) => {
for rule in rules {
emit_rule(output, rule, depth + 1);
}
}
StyleAtRuleBlock::Declarations(declarations) => {
emit_declarations(output, declarations, depth + 1);
}
}
output.push_str(&format!("{indent}}}\n"));
}
}
}
StyleRule::Keyframes {
scoped_name,
vendor_prefix,
frames,
..
} => {
output.push_str(&format!(
"{indent}@{}keyframes {scoped_name} {{\n",
vendor_prefix.as_deref().unwrap_or("")
));
for frame in frames {
emit_keyframe(output, frame, depth + 1);
}
output.push_str(&format!("{indent}}}\n"));
}
}
}
fn emit_keyframe(output: &mut String, frame: &Keyframe, depth: usize) {
let indent = " ".repeat(depth);
output.push_str(&format!("{indent}{} {{\n", frame.selector));
emit_declarations(output, &frame.declarations, depth + 1);
output.push_str(&format!("{indent}}}\n"));
}
fn emit_declarations(output: &mut String, declarations: &[StyleDeclaration], depth: usize) {
let indent = " ".repeat(depth);
for declaration in declarations {
let important = if declaration.important {
" !important"
} else {
""
};
output.push_str(&format!(
"{indent}{}: {}{important};\n",
declaration.name, declaration.value
));
}
}
#[cfg(test)]
mod tests {
use super::*;
use noxid_css_ir::{StyleRule, StyleSheet};
use noxid_ir::SemanticId;
use noxid_source::Span;
#[test]
fn emits_scoped_rules_and_at_rules() {
let program = StyleProgram {
sheets: vec![StyleSheet {
id: SemanticId::style("Panel"),
component: SemanticId::component("Panel"),
component_name: "Panel".into(),
scope: "noxid-test".into(),
cst_token_count: 4,
span: Span::new(0, 10),
rules: vec![StyleRule::Qualified {
id: SemanticId::css_rule("Panel", 1),
selector: ".panel".into(),
scoped_selector: ".panel[data-noxid-scope=\"noxid-test\"]".into(),
declarations: vec![StyleDeclaration {
id: SemanticId::css_declaration("Panel", 1, 1),
name: "color".into(),
value: "red".into(),
important: false,
span: Span::new(0, 10),
}],
span: Span::new(0, 10),
}],
}],
};
assert_eq!(
generate(&program),
".panel[data-noxid-scope=\"noxid-test\"] {\n color: red;\n}\n"
);
}
}