1use alloc::{format, string::String, string::ToString, vec, vec::Vec};
7use core::fmt::Write;
8
9use super::{CodegenBackend, GeneratedFile};
10use crate::{
11 css::{
12 AttributeMatchOp, Css, CssAttributeSelector, CssDeclaration, CssNthChildPattern,
13 CssNthChildSelector, CssPath, CssPathPseudoSelector, CssPathSelector, DynamicCssProperty,
14 NodeTypeTag,
15 },
16 props::property::format_static_css_prop,
17};
18
19#[derive(Copy, Clone, Debug)]
21pub struct RustBackend;
22
23impl CodegenBackend for RustBackend {
24 fn lang(&self) -> &'static str {
25 "rust"
26 }
27
28 fn emit_css(&self, css: &Css) -> String {
29 css_to_rust_code(css)
30 }
31
32 fn emit_project(&self, css: &Css) -> Vec<GeneratedFile> {
33 let css_literal = css_to_rust_code(css);
34 let main_rs = format!(
35 "use azul::prelude::*;\r\n\r\n{css_literal}\r\n\r\nfn main() {{\r\n \
36 println!(\"Generated stylesheet contains {{}} rule(s)\", \
37 CSS.rules.as_ref().len());\r\n}}\r\n",
38 );
39 let cargo_toml = "[package]\r\n\
40 name = \"azul-generated-app\"\r\n\
41 version = \"0.1.0\"\r\n\
42 edition = \"2021\"\r\n\
43 \r\n\
44 [dependencies]\r\n\
45 azul = \"0.0.7\"\r\n"
46 .to_string();
47 vec![
48 GeneratedFile {
49 path: "Cargo.toml".to_string(),
50 contents: cargo_toml,
51 },
52 GeneratedFile {
53 path: "src/main.rs".to_string(),
54 contents: main_rs,
55 },
56 ]
57 }
58}
59
60#[must_use] pub fn css_to_rust_code(css: &Css) -> String {
62 let mut output = String::new();
63
64 output.push_str("const CSS: Css = Css {\r\n");
65 output.push_str("\trules: [\r\n");
66
67 for block in &css.rules {
68 output.push_str("\t\tCssRuleBlock {\r\n");
69 let _ = write!(output,
70 "\t\t\tpath: {},\r\n",
71 print_block_path(&block.path, 3)
72 );
73 let _ = write!(output,
74 "\t\t\tpriority: {},\r\n",
75 block.priority,
76 );
77
78 output.push_str("\t\t\tdeclarations: [\r\n");
79 for declaration in &block.declarations {
80 let _ = write!(output,
81 "\t\t\t\t{},\r\n",
82 print_declaration(declaration, 4)
83 );
84 }
85 output.push_str("\t\t\t]\r\n");
86
87 output.push_str("\t\t},\r\n");
88 }
89
90 output.push_str("\t]\r\n");
91 output.push_str("};");
92
93 output.replace('\t', " ")
94}
95
96#[allow(clippy::too_many_lines)] #[must_use] pub const fn format_node_type(n: &NodeTypeTag) -> &'static str {
98 match n {
99 NodeTypeTag::Html => "NodeTypeTag::Html",
101 NodeTypeTag::Head => "NodeTypeTag::Head",
102 NodeTypeTag::Body => "NodeTypeTag::Body",
103
104 NodeTypeTag::Div => "NodeTypeTag::Div",
106 NodeTypeTag::P => "NodeTypeTag::P",
107 NodeTypeTag::Article => "NodeTypeTag::Article",
108 NodeTypeTag::Section => "NodeTypeTag::Section",
109 NodeTypeTag::Nav => "NodeTypeTag::Nav",
110 NodeTypeTag::Aside => "NodeTypeTag::Aside",
111 NodeTypeTag::Header => "NodeTypeTag::Header",
112 NodeTypeTag::Footer => "NodeTypeTag::Footer",
113 NodeTypeTag::Main => "NodeTypeTag::Main",
114 NodeTypeTag::Figure => "NodeTypeTag::Figure",
115 NodeTypeTag::FigCaption => "NodeTypeTag::FigCaption",
116
117 NodeTypeTag::H1 => "NodeTypeTag::H1",
119 NodeTypeTag::H2 => "NodeTypeTag::H2",
120 NodeTypeTag::H3 => "NodeTypeTag::H3",
121 NodeTypeTag::H4 => "NodeTypeTag::H4",
122 NodeTypeTag::H5 => "NodeTypeTag::H5",
123 NodeTypeTag::H6 => "NodeTypeTag::H6",
124
125 NodeTypeTag::Br => "NodeTypeTag::Br",
127 NodeTypeTag::Hr => "NodeTypeTag::Hr",
128 NodeTypeTag::Pre => "NodeTypeTag::Pre",
129 NodeTypeTag::BlockQuote => "NodeTypeTag::BlockQuote",
130 NodeTypeTag::Address => "NodeTypeTag::Address",
131 NodeTypeTag::Details => "NodeTypeTag::Details",
132 NodeTypeTag::Summary => "NodeTypeTag::Summary",
133 NodeTypeTag::Dialog => "NodeTypeTag::Dialog",
134
135 NodeTypeTag::Ul => "NodeTypeTag::Ul",
137 NodeTypeTag::Ol => "NodeTypeTag::Ol",
138 NodeTypeTag::Li => "NodeTypeTag::Li",
139 NodeTypeTag::Dl => "NodeTypeTag::Dl",
140 NodeTypeTag::Dt => "NodeTypeTag::Dt",
141 NodeTypeTag::Dd => "NodeTypeTag::Dd",
142 NodeTypeTag::Menu => "NodeTypeTag::Menu",
143 NodeTypeTag::MenuItem => "NodeTypeTag::MenuItem",
144 NodeTypeTag::Dir => "NodeTypeTag::Dir",
145
146 NodeTypeTag::Table => "NodeTypeTag::Table",
148 NodeTypeTag::Caption => "NodeTypeTag::Caption",
149 NodeTypeTag::THead => "NodeTypeTag::THead",
150 NodeTypeTag::TBody => "NodeTypeTag::TBody",
151 NodeTypeTag::TFoot => "NodeTypeTag::TFoot",
152 NodeTypeTag::Tr => "NodeTypeTag::Tr",
153 NodeTypeTag::Th => "NodeTypeTag::Th",
154 NodeTypeTag::Td => "NodeTypeTag::Td",
155 NodeTypeTag::ColGroup => "NodeTypeTag::ColGroup",
156 NodeTypeTag::Col => "NodeTypeTag::Col",
157
158 NodeTypeTag::Form => "NodeTypeTag::Form",
160 NodeTypeTag::FieldSet => "NodeTypeTag::FieldSet",
161 NodeTypeTag::Legend => "NodeTypeTag::Legend",
162 NodeTypeTag::Label => "NodeTypeTag::Label",
163 NodeTypeTag::Input => "NodeTypeTag::Input",
164 NodeTypeTag::Button => "NodeTypeTag::Button",
165 NodeTypeTag::Select => "NodeTypeTag::Select",
166 NodeTypeTag::OptGroup => "NodeTypeTag::OptGroup",
167 NodeTypeTag::SelectOption => "NodeTypeTag::SelectOption",
168 NodeTypeTag::TextArea => "NodeTypeTag::TextArea",
169 NodeTypeTag::Output => "NodeTypeTag::Output",
170 NodeTypeTag::Progress => "NodeTypeTag::Progress",
171 NodeTypeTag::Meter => "NodeTypeTag::Meter",
172 NodeTypeTag::DataList => "NodeTypeTag::DataList",
173
174 NodeTypeTag::Span => "NodeTypeTag::Span",
176 NodeTypeTag::A => "NodeTypeTag::A",
177 NodeTypeTag::Em => "NodeTypeTag::Em",
178 NodeTypeTag::Strong => "NodeTypeTag::Strong",
179 NodeTypeTag::B => "NodeTypeTag::B",
180 NodeTypeTag::I => "NodeTypeTag::I",
181 NodeTypeTag::U => "NodeTypeTag::U",
182 NodeTypeTag::S => "NodeTypeTag::S",
183 NodeTypeTag::Mark => "NodeTypeTag::Mark",
184 NodeTypeTag::Del => "NodeTypeTag::Del",
185 NodeTypeTag::Ins => "NodeTypeTag::Ins",
186 NodeTypeTag::Code => "NodeTypeTag::Code",
187 NodeTypeTag::Samp => "NodeTypeTag::Samp",
188 NodeTypeTag::Kbd => "NodeTypeTag::Kbd",
189 NodeTypeTag::Var => "NodeTypeTag::Var",
190 NodeTypeTag::Cite => "NodeTypeTag::Cite",
191 NodeTypeTag::Dfn => "NodeTypeTag::Dfn",
192 NodeTypeTag::Abbr => "NodeTypeTag::Abbr",
193 NodeTypeTag::Acronym => "NodeTypeTag::Acronym",
194 NodeTypeTag::Q => "NodeTypeTag::Q",
195 NodeTypeTag::Time => "NodeTypeTag::Time",
196 NodeTypeTag::Sub => "NodeTypeTag::Sub",
197 NodeTypeTag::Sup => "NodeTypeTag::Sup",
198 NodeTypeTag::Small => "NodeTypeTag::Small",
199 NodeTypeTag::Big => "NodeTypeTag::Big",
200 NodeTypeTag::Bdo => "NodeTypeTag::Bdo",
201 NodeTypeTag::Bdi => "NodeTypeTag::Bdi",
202 NodeTypeTag::Wbr => "NodeTypeTag::Wbr",
203 NodeTypeTag::Ruby => "NodeTypeTag::Ruby",
204 NodeTypeTag::Rt => "NodeTypeTag::Rt",
205 NodeTypeTag::Rtc => "NodeTypeTag::Rtc",
206 NodeTypeTag::Rp => "NodeTypeTag::Rp",
207 NodeTypeTag::Data => "NodeTypeTag::Data",
208
209 NodeTypeTag::Canvas => "NodeTypeTag::Canvas",
211 NodeTypeTag::Object => "NodeTypeTag::Object",
212 NodeTypeTag::Param => "NodeTypeTag::Param",
213 NodeTypeTag::Embed => "NodeTypeTag::Embed",
214 NodeTypeTag::Audio => "NodeTypeTag::Audio",
215 NodeTypeTag::Video => "NodeTypeTag::Video",
216 NodeTypeTag::Source => "NodeTypeTag::Source",
217 NodeTypeTag::Track => "NodeTypeTag::Track",
218 NodeTypeTag::Map => "NodeTypeTag::Map",
219 NodeTypeTag::Area => "NodeTypeTag::Area",
220 NodeTypeTag::Svg => "NodeTypeTag::Svg",
221 NodeTypeTag::SvgPath => "NodeTypeTag::SvgPath",
222 NodeTypeTag::SvgCircle => "NodeTypeTag::SvgCircle",
223 NodeTypeTag::SvgRect => "NodeTypeTag::SvgRect",
224 NodeTypeTag::SvgEllipse => "NodeTypeTag::SvgEllipse",
225 NodeTypeTag::SvgLine => "NodeTypeTag::SvgLine",
226 NodeTypeTag::SvgPolygon => "NodeTypeTag::SvgPolygon",
227 NodeTypeTag::SvgPolyline => "NodeTypeTag::SvgPolyline",
228 NodeTypeTag::SvgG => "NodeTypeTag::SvgG",
229
230 NodeTypeTag::SvgDefs => "NodeTypeTag::SvgDefs",
232 NodeTypeTag::SvgSymbol => "NodeTypeTag::SvgSymbol",
233 NodeTypeTag::SvgUse => "NodeTypeTag::SvgUse",
234 NodeTypeTag::SvgSwitch => "NodeTypeTag::SvgSwitch",
235
236 NodeTypeTag::SvgText => "NodeTypeTag::SvgText",
238 NodeTypeTag::SvgTspan => "NodeTypeTag::SvgTspan",
239 NodeTypeTag::SvgTextPath => "NodeTypeTag::SvgTextPath",
240
241 NodeTypeTag::SvgLinearGradient => "NodeTypeTag::SvgLinearGradient",
243 NodeTypeTag::SvgRadialGradient => "NodeTypeTag::SvgRadialGradient",
244 NodeTypeTag::SvgStop => "NodeTypeTag::SvgStop",
245 NodeTypeTag::SvgPattern => "NodeTypeTag::SvgPattern",
246
247 NodeTypeTag::SvgClipPathElement => "NodeTypeTag::SvgClipPathElement",
249 NodeTypeTag::SvgMask => "NodeTypeTag::SvgMask",
250
251 NodeTypeTag::SvgFilter => "NodeTypeTag::SvgFilter",
253 NodeTypeTag::SvgFeBlend => "NodeTypeTag::SvgFeBlend",
254 NodeTypeTag::SvgFeColorMatrix => "NodeTypeTag::SvgFeColorMatrix",
255 NodeTypeTag::SvgFeComponentTransfer => "NodeTypeTag::SvgFeComponentTransfer",
256 NodeTypeTag::SvgFeComposite => "NodeTypeTag::SvgFeComposite",
257 NodeTypeTag::SvgFeConvolveMatrix => "NodeTypeTag::SvgFeConvolveMatrix",
258 NodeTypeTag::SvgFeDiffuseLighting => "NodeTypeTag::SvgFeDiffuseLighting",
259 NodeTypeTag::SvgFeDisplacementMap => "NodeTypeTag::SvgFeDisplacementMap",
260 NodeTypeTag::SvgFeDistantLight => "NodeTypeTag::SvgFeDistantLight",
261 NodeTypeTag::SvgFeDropShadow => "NodeTypeTag::SvgFeDropShadow",
262 NodeTypeTag::SvgFeFlood => "NodeTypeTag::SvgFeFlood",
263 NodeTypeTag::SvgFeFuncR => "NodeTypeTag::SvgFeFuncR",
264 NodeTypeTag::SvgFeFuncG => "NodeTypeTag::SvgFeFuncG",
265 NodeTypeTag::SvgFeFuncB => "NodeTypeTag::SvgFeFuncB",
266 NodeTypeTag::SvgFeFuncA => "NodeTypeTag::SvgFeFuncA",
267 NodeTypeTag::SvgFeGaussianBlur => "NodeTypeTag::SvgFeGaussianBlur",
268 NodeTypeTag::SvgFeImage => "NodeTypeTag::SvgFeImage",
269 NodeTypeTag::SvgFeMerge => "NodeTypeTag::SvgFeMerge",
270 NodeTypeTag::SvgFeMergeNode => "NodeTypeTag::SvgFeMergeNode",
271 NodeTypeTag::SvgFeMorphology => "NodeTypeTag::SvgFeMorphology",
272 NodeTypeTag::SvgFeOffset => "NodeTypeTag::SvgFeOffset",
273 NodeTypeTag::SvgFePointLight => "NodeTypeTag::SvgFePointLight",
274 NodeTypeTag::SvgFeSpecularLighting => "NodeTypeTag::SvgFeSpecularLighting",
275 NodeTypeTag::SvgFeSpotLight => "NodeTypeTag::SvgFeSpotLight",
276 NodeTypeTag::SvgFeTile => "NodeTypeTag::SvgFeTile",
277 NodeTypeTag::SvgFeTurbulence => "NodeTypeTag::SvgFeTurbulence",
278
279 NodeTypeTag::SvgMarker => "NodeTypeTag::SvgMarker",
281 NodeTypeTag::SvgImage => "NodeTypeTag::SvgImage",
282 NodeTypeTag::SvgForeignObject => "NodeTypeTag::SvgForeignObject",
283
284 NodeTypeTag::SvgTitle => "NodeTypeTag::SvgTitle",
286 NodeTypeTag::SvgDesc => "NodeTypeTag::SvgDesc",
287 NodeTypeTag::SvgMetadata => "NodeTypeTag::SvgMetadata",
288 NodeTypeTag::SvgA => "NodeTypeTag::SvgA",
289 NodeTypeTag::SvgView => "NodeTypeTag::SvgView",
290 NodeTypeTag::SvgStyle => "NodeTypeTag::SvgStyle",
291 NodeTypeTag::SvgScript => "NodeTypeTag::SvgScript",
292
293 NodeTypeTag::SvgAnimate => "NodeTypeTag::SvgAnimate",
295 NodeTypeTag::SvgAnimateMotion => "NodeTypeTag::SvgAnimateMotion",
296 NodeTypeTag::SvgAnimateTransform => "NodeTypeTag::SvgAnimateTransform",
297 NodeTypeTag::SvgSet => "NodeTypeTag::SvgSet",
298 NodeTypeTag::SvgMpath => "NodeTypeTag::SvgMpath",
299
300 NodeTypeTag::Title => "NodeTypeTag::Title",
302 NodeTypeTag::Meta => "NodeTypeTag::Meta",
303 NodeTypeTag::Link => "NodeTypeTag::Link",
304 NodeTypeTag::Script => "NodeTypeTag::Script",
305 NodeTypeTag::Style => "NodeTypeTag::Style",
306 NodeTypeTag::Base => "NodeTypeTag::Base",
307
308 NodeTypeTag::Text => "NodeTypeTag::Text",
310 NodeTypeTag::Img => "NodeTypeTag::Img",
311 NodeTypeTag::VirtualView => "NodeTypeTag::VirtualView",
312 NodeTypeTag::Icon => "NodeTypeTag::Icon",
313 NodeTypeTag::GeolocationProbe => "NodeTypeTag::GeolocationProbe",
314
315 NodeTypeTag::Before => "NodeTypeTag::Before",
317 NodeTypeTag::After => "NodeTypeTag::After",
318 NodeTypeTag::Marker => "NodeTypeTag::Marker",
319 NodeTypeTag::Placeholder => "NodeTypeTag::Placeholder",
320 }
321}
322
323#[must_use] pub fn print_block_path(path: &CssPath, tabs: usize) -> String {
324 let t = String::from(" ").repeat(tabs);
325 let t1 = String::from(" ").repeat(tabs + 1);
326
327 format!(
328 "CssPath {{\r\n{}selectors: {}\r\n{}}}",
329 t1,
330 format_selectors(path.selectors.as_ref(), tabs + 1),
331 t
332 )
333}
334
335#[must_use] pub fn format_selectors(selectors: &[CssPathSelector], tabs: usize) -> String {
336 let t = String::from(" ").repeat(tabs);
337 let t1 = String::from(" ").repeat(tabs + 1);
338
339 let selectors_formatted = selectors
340 .iter()
341 .map(|s| format!("{}{},", t1, format_single_selector(s, tabs + 1)))
342 .collect::<Vec<String>>()
343 .join("\r\n");
344
345 format!("vec![\r\n{selectors_formatted}\r\n{t}].into()")
346}
347
348#[must_use] pub fn format_single_selector(p: &CssPathSelector, _tabs: usize) -> String {
349 match p {
350 CssPathSelector::Global => "CssPathSelector::Global".to_string(),
351 CssPathSelector::Root(r) => format!(
352 "CssPathSelector::Root(CssScopeRange {{ start: {}, end: {} }})",
353 r.start, r.end
354 ),
355 CssPathSelector::Type(ntp) => format!("CssPathSelector::Type({})", format_node_type(ntp)),
356 CssPathSelector::Class(class) => {
357 format!("CssPathSelector::Class(String::from({class:?}))")
358 }
359 CssPathSelector::Id(id) => format!("CssPathSelector::Id(String::from({id:?}))"),
360 CssPathSelector::PseudoSelector(cps) => format!(
361 "CssPathSelector::PseudoSelector({})",
362 format_pseudo_selector_type(cps)
363 ),
364 CssPathSelector::Attribute(a) => format!(
365 "CssPathSelector::Attribute({})",
366 format_attribute_selector(a)
367 ),
368 CssPathSelector::DirectChildren => "CssPathSelector::DirectChildren".to_string(),
369 CssPathSelector::Children => "CssPathSelector::Children".to_string(),
370 CssPathSelector::AdjacentSibling => "CssPathSelector::AdjacentSibling".to_string(),
371 CssPathSelector::GeneralSibling => "CssPathSelector::GeneralSibling".to_string(),
372 }
373}
374
375#[must_use] pub fn format_pseudo_selector_type(p: &CssPathPseudoSelector) -> String {
376 match p {
377 CssPathPseudoSelector::First => "CssPathPseudoSelector::First".to_string(),
378 CssPathPseudoSelector::Last => "CssPathPseudoSelector::Last".to_string(),
379 CssPathPseudoSelector::NthChild(n) => format!(
380 "CssPathPseudoSelector::NthChild({})",
381 format_nth_child_selector(n)
382 ),
383 CssPathPseudoSelector::Hover => "CssPathPseudoSelector::Hover".to_string(),
384 CssPathPseudoSelector::Active => "CssPathPseudoSelector::Active".to_string(),
385 CssPathPseudoSelector::Focus => "CssPathPseudoSelector::Focus".to_string(),
386 CssPathPseudoSelector::Backdrop => "CssPathPseudoSelector::Backdrop".to_string(),
387 CssPathPseudoSelector::Lang(lang) => format!(
388 "CssPathPseudoSelector::Lang(AzString::from_const_str(\"{}\"))",
389 lang.as_str()
390 ),
391 CssPathPseudoSelector::Dragging => "CssPathPseudoSelector::Dragging".to_string(),
392 CssPathPseudoSelector::DragOver => "CssPathPseudoSelector::DragOver".to_string(),
393 CssPathPseudoSelector::Root => "CssPathPseudoSelector::Root".to_string(),
394 }
395}
396
397#[must_use] pub fn format_attribute_selector(a: &CssAttributeSelector) -> String {
398 let value = a.value.as_ref().map_or_else(
399 || "OptionString::None".to_string(),
400 |v| {
401 format!(
402 "OptionString::Some(AzString::from_const_str({:?}))",
403 v.as_str()
404 )
405 },
406 );
407 format!(
408 "CssAttributeSelector {{ name: AzString::from_const_str({:?}), op: {}, value: {} }}",
409 a.name.as_str(),
410 format_attribute_match_op(&a.op),
411 value
412 )
413}
414
415#[must_use] pub fn format_attribute_match_op(op: &AttributeMatchOp) -> String {
416 match op {
417 AttributeMatchOp::Exists => "AttributeMatchOp::Exists".to_string(),
418 AttributeMatchOp::Eq => "AttributeMatchOp::Eq".to_string(),
419 AttributeMatchOp::Includes => "AttributeMatchOp::Includes".to_string(),
420 AttributeMatchOp::DashMatch => "AttributeMatchOp::DashMatch".to_string(),
421 AttributeMatchOp::Prefix => "AttributeMatchOp::Prefix".to_string(),
422 AttributeMatchOp::Suffix => "AttributeMatchOp::Suffix".to_string(),
423 AttributeMatchOp::Substring => "AttributeMatchOp::Substring".to_string(),
424 }
425}
426
427#[must_use] pub fn format_nth_child_selector(n: &CssNthChildSelector) -> String {
428 match n {
429 CssNthChildSelector::Number(num) => format!("CssNthChildSelector::Number({num})"),
430 CssNthChildSelector::Even => "CssNthChildSelector::Even".to_string(),
431 CssNthChildSelector::Odd => "CssNthChildSelector::Odd".to_string(),
432 CssNthChildSelector::Pattern(CssNthChildPattern {
433 pattern_repeat,
434 offset,
435 }) => format!(
436 "CssNthChildSelector::Pattern(CssNthChildPattern {{ pattern_repeat: {pattern_repeat}, offset: {offset} }})"
437 ),
438 }
439}
440
441#[must_use] pub fn print_declaration(decl: &CssDeclaration, tabs: usize) -> String {
442 match decl {
443 CssDeclaration::Static(s) => format!(
444 "CssDeclaration::Static({})",
445 format_static_css_prop(s, tabs)
446 ),
447 CssDeclaration::Dynamic(d) => format!(
448 "CssDeclaration::Dynamic({})",
449 format_dynamic_css_prop(d, tabs)
450 ),
451 }
452}
453
454#[must_use] pub fn format_dynamic_css_prop(decl: &DynamicCssProperty, tabs: usize) -> String {
455 let t = String::from(" ").repeat(tabs);
456 format!(
457 "DynamicCssProperty {{\r\n{} dynamic_id: {:?},\r\n{} default_value: {},\r\n{}}}",
458 t,
459 decl.dynamic_id,
460 t,
461 format_static_css_prop(&decl.default_value, tabs + 1),
462 t
463 )
464}
465
466#[cfg(test)]
467mod tests {
468 use alloc::vec;
469
470 use super::*;
471 use crate::css::CssRuleBlock;
472
473 fn sample_css() -> Css {
474 let path = CssPath::new(vec![CssPathSelector::Type(NodeTypeTag::Div)]);
475 let block = CssRuleBlock::new(path, vec![]);
476 Css {
477 rules: vec![block].into(),
478 }
479 }
480
481 #[test]
482 fn rust_backend_emits_const_literal() {
483 let css = sample_css();
484 let rust = RustBackend.emit_css(&css);
485 assert!(rust.contains("const CSS: Css"));
486 assert!(rust.contains("NodeTypeTag::Div"));
487 }
488
489 #[test]
490 fn rust_backend_emits_project_files() {
491 let css = sample_css();
492 let files = RustBackend.emit_project(&css);
493 let paths: Vec<_> = files.iter().map(|f| f.path.as_str()).collect();
494 assert!(paths.contains(&"Cargo.toml"));
495 assert!(paths.contains(&"src/main.rs"));
496 let main_rs = files
497 .iter()
498 .find(|f| f.path == "src/main.rs")
499 .expect("main.rs missing");
500 assert!(main_rs.contains_const_literal());
501 }
502
503 impl GeneratedFile {
504 fn contains_const_literal(&self) -> bool {
505 self.contents.contains("const CSS: Css")
506 }
507 }
508}