1
//! Rust source-code emitter for parsed CSS.
2
//!
3
//! Produces a `const CSS: Css = ...;` literal plus a minimal `Cargo.toml` and
4
//! `src/main.rs` skeleton suitable for `cargo build` against `azul`.
5

            
6
use alloc::{format, string::String, string::ToString, vec, vec::Vec};
7
use core::fmt::Write;
8

            
9
use super::{CodegenBackend, GeneratedFile};
10
use 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
/// Emits Rust source code for a parsed CSS stylesheet.
20
#[derive(Copy, Clone, Debug)]
21
pub struct RustBackend;
22

            
23
impl CodegenBackend for RustBackend {
24
    fn lang(&self) -> &'static str {
25
        "rust"
26
    }
27

            
28
1
    fn emit_css(&self, css: &Css) -> String {
29
1
        css_to_rust_code(css)
30
1
    }
31

            
32
1
    fn emit_project(&self, css: &Css) -> Vec<GeneratedFile> {
33
1
        let css_literal = css_to_rust_code(css);
34
1
        let main_rs = format!(
35
1
            "use azul::prelude::*;\r\n\r\n{css_literal}\r\n\r\nfn main() {{\r\n    \
36
1
             println!(\"Generated stylesheet contains {{}} rule(s)\", \
37
1
             CSS.rules.as_ref().len());\r\n}}\r\n",
38
        );
39
1
        let cargo_toml = "[package]\r\n\
40
1
            name = \"azul-generated-app\"\r\n\
41
1
            version = \"0.1.0\"\r\n\
42
1
            edition = \"2021\"\r\n\
43
1
            \r\n\
44
1
            [dependencies]\r\n\
45
1
            azul = \"0.0.7\"\r\n"
46
1
            .to_string();
47
1
        vec![
48
1
            GeneratedFile {
49
1
                path: "Cargo.toml".to_string(),
50
1
                contents: cargo_toml,
51
1
            },
52
1
            GeneratedFile {
53
1
                path: "src/main.rs".to_string(),
54
1
                contents: main_rs,
55
1
            },
56
        ]
57
1
    }
58
}
59

            
60
/// Render a parsed [`Css`] as Rust source code (a `const CSS: Css = ...;`).
61
2
#[must_use] pub fn css_to_rust_code(css: &Css) -> String {
62
2
    let mut output = String::new();
63

            
64
2
    output.push_str("const CSS: Css = Css {\r\n");
65
2
    output.push_str("\trules: [\r\n");
66

            
67
4
    for block in &css.rules {
68
2
        output.push_str("\t\tCssRuleBlock {\r\n");
69
2
        let _ = write!(output,
70
2
            "\t\t\tpath: {},\r\n",
71
2
            print_block_path(&block.path, 3)
72
        );
73
2
        let _ = write!(output,
74
2
            "\t\t\tpriority: {},\r\n",
75
            block.priority,
76
        );
77

            
78
2
        output.push_str("\t\t\tdeclarations: [\r\n");
79
2
        for declaration in &block.declarations {
80
            let _ = write!(output,
81
                "\t\t\t\t{},\r\n",
82
                print_declaration(declaration, 4)
83
            );
84
        }
85
2
        output.push_str("\t\t\t]\r\n");
86

            
87
2
        output.push_str("\t\t},\r\n");
88
    }
89

            
90
2
    output.push_str("\t]\r\n");
91
2
    output.push_str("};");
92

            
93
2
    output.replace('\t', "    ")
94
2
}
95

            
96
#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
97
2
#[must_use] pub const fn format_node_type(n: &NodeTypeTag) -> &'static str {
98
2
    match n {
99
        // Document structure
100
        NodeTypeTag::Html => "NodeTypeTag::Html",
101
        NodeTypeTag::Head => "NodeTypeTag::Head",
102
        NodeTypeTag::Body => "NodeTypeTag::Body",
103

            
104
        // Block elements
105
2
        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
        // Headings
118
        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
        // Text formatting
126
        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
        // List elements
136
        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
        // Table elements
147
        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
        // Form elements
159
        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
        // Inline elements
175
        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
        // Embedded content
210
        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
        // SVG container elements
231
        NodeTypeTag::SvgDefs => "NodeTypeTag::SvgDefs",
232
        NodeTypeTag::SvgSymbol => "NodeTypeTag::SvgSymbol",
233
        NodeTypeTag::SvgUse => "NodeTypeTag::SvgUse",
234
        NodeTypeTag::SvgSwitch => "NodeTypeTag::SvgSwitch",
235

            
236
        // SVG text elements
237
        NodeTypeTag::SvgText => "NodeTypeTag::SvgText",
238
        NodeTypeTag::SvgTspan => "NodeTypeTag::SvgTspan",
239
        NodeTypeTag::SvgTextPath => "NodeTypeTag::SvgTextPath",
240

            
241
        // SVG paint server elements
242
        NodeTypeTag::SvgLinearGradient => "NodeTypeTag::SvgLinearGradient",
243
        NodeTypeTag::SvgRadialGradient => "NodeTypeTag::SvgRadialGradient",
244
        NodeTypeTag::SvgStop => "NodeTypeTag::SvgStop",
245
        NodeTypeTag::SvgPattern => "NodeTypeTag::SvgPattern",
246

            
247
        // SVG clipping/masking elements
248
        NodeTypeTag::SvgClipPathElement => "NodeTypeTag::SvgClipPathElement",
249
        NodeTypeTag::SvgMask => "NodeTypeTag::SvgMask",
250

            
251
        // SVG filter elements
252
        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
        // SVG marker/image elements
280
        NodeTypeTag::SvgMarker => "NodeTypeTag::SvgMarker",
281
        NodeTypeTag::SvgImage => "NodeTypeTag::SvgImage",
282
        NodeTypeTag::SvgForeignObject => "NodeTypeTag::SvgForeignObject",
283

            
284
        // SVG descriptive elements
285
        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
        // SVG animation elements
294
        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
        // Metadata
301
        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
        // Content elements
309
        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
        NodeTypeTag::PageBreak => "NodeTypeTag::PageBreak",
315

            
316
        // Pseudo-elements
317
        NodeTypeTag::Before => "NodeTypeTag::Before",
318
        NodeTypeTag::After => "NodeTypeTag::After",
319
        NodeTypeTag::Marker => "NodeTypeTag::Marker",
320
        NodeTypeTag::Placeholder => "NodeTypeTag::Placeholder",
321
    }
322
2
}
323

            
324
2
#[must_use] pub fn print_block_path(path: &CssPath, tabs: usize) -> String {
325
2
    let t = String::from("    ").repeat(tabs);
326
2
    let t1 = String::from("    ").repeat(tabs + 1);
327

            
328
2
    format!(
329
2
        "CssPath {{\r\n{}selectors: {}\r\n{}}}",
330
        t1,
331
2
        format_selectors(path.selectors.as_ref(), tabs + 1),
332
        t
333
    )
334
2
}
335

            
336
2
#[must_use] pub fn format_selectors(selectors: &[CssPathSelector], tabs: usize) -> String {
337
2
    let t = String::from("    ").repeat(tabs);
338
2
    let t1 = String::from("    ").repeat(tabs + 1);
339

            
340
2
    let selectors_formatted = selectors
341
2
        .iter()
342
2
        .map(|s| format!("{}{},", t1, format_single_selector(s, tabs + 1)))
343
2
        .collect::<Vec<String>>()
344
2
        .join("\r\n");
345

            
346
2
    format!("vec![\r\n{selectors_formatted}\r\n{t}].into()")
347
2
}
348

            
349
2
#[must_use] pub fn format_single_selector(p: &CssPathSelector, _tabs: usize) -> String {
350
2
    match p {
351
        CssPathSelector::Global => "CssPathSelector::Global".to_string(),
352
        CssPathSelector::Root(r) => format!(
353
            "CssPathSelector::Root(CssScopeRange {{ start: {}, end: {} }})",
354
            r.start, r.end
355
        ),
356
2
        CssPathSelector::Type(ntp) => format!("CssPathSelector::Type({})", format_node_type(ntp)),
357
        CssPathSelector::Class(class) => {
358
            format!("CssPathSelector::Class(String::from({class:?}))")
359
        }
360
        CssPathSelector::Id(id) => format!("CssPathSelector::Id(String::from({id:?}))"),
361
        CssPathSelector::PseudoSelector(cps) => format!(
362
            "CssPathSelector::PseudoSelector({})",
363
            format_pseudo_selector_type(cps)
364
        ),
365
        CssPathSelector::Attribute(a) => format!(
366
            "CssPathSelector::Attribute({})",
367
            format_attribute_selector(a)
368
        ),
369
        CssPathSelector::DirectChildren => "CssPathSelector::DirectChildren".to_string(),
370
        CssPathSelector::Children => "CssPathSelector::Children".to_string(),
371
        CssPathSelector::AdjacentSibling => "CssPathSelector::AdjacentSibling".to_string(),
372
        CssPathSelector::GeneralSibling => "CssPathSelector::GeneralSibling".to_string(),
373
    }
374
2
}
375

            
376
#[must_use] pub fn format_pseudo_selector_type(p: &CssPathPseudoSelector) -> String {
377
    match p {
378
        CssPathPseudoSelector::First => "CssPathPseudoSelector::First".to_string(),
379
        CssPathPseudoSelector::Last => "CssPathPseudoSelector::Last".to_string(),
380
        CssPathPseudoSelector::NthChild(n) => format!(
381
            "CssPathPseudoSelector::NthChild({})",
382
            format_nth_child_selector(n)
383
        ),
384
        CssPathPseudoSelector::Hover => "CssPathPseudoSelector::Hover".to_string(),
385
        CssPathPseudoSelector::Active => "CssPathPseudoSelector::Active".to_string(),
386
        CssPathPseudoSelector::Focus => "CssPathPseudoSelector::Focus".to_string(),
387
        CssPathPseudoSelector::Backdrop => "CssPathPseudoSelector::Backdrop".to_string(),
388
        CssPathPseudoSelector::Lang(lang) => format!(
389
            "CssPathPseudoSelector::Lang(AzString::from_const_str(\"{}\"))",
390
            lang.as_str()
391
        ),
392
        CssPathPseudoSelector::Dragging => "CssPathPseudoSelector::Dragging".to_string(),
393
        CssPathPseudoSelector::DragOver => "CssPathPseudoSelector::DragOver".to_string(),
394
        CssPathPseudoSelector::Root => "CssPathPseudoSelector::Root".to_string(),
395
    }
396
}
397

            
398
#[must_use] pub fn format_attribute_selector(a: &CssAttributeSelector) -> String {
399
    let value = a.value.as_ref().map_or_else(
400
        || "OptionString::None".to_string(),
401
        |v| {
402
            format!(
403
                "OptionString::Some(AzString::from_const_str({:?}))",
404
                v.as_str()
405
            )
406
        },
407
    );
408
    format!(
409
        "CssAttributeSelector {{ name: AzString::from_const_str({:?}), op: {}, value: {} }}",
410
        a.name.as_str(),
411
        format_attribute_match_op(&a.op),
412
        value
413
    )
414
}
415

            
416
#[must_use] pub fn format_attribute_match_op(op: &AttributeMatchOp) -> String {
417
    match op {
418
        AttributeMatchOp::Exists => "AttributeMatchOp::Exists".to_string(),
419
        AttributeMatchOp::Eq => "AttributeMatchOp::Eq".to_string(),
420
        AttributeMatchOp::Includes => "AttributeMatchOp::Includes".to_string(),
421
        AttributeMatchOp::DashMatch => "AttributeMatchOp::DashMatch".to_string(),
422
        AttributeMatchOp::Prefix => "AttributeMatchOp::Prefix".to_string(),
423
        AttributeMatchOp::Suffix => "AttributeMatchOp::Suffix".to_string(),
424
        AttributeMatchOp::Substring => "AttributeMatchOp::Substring".to_string(),
425
    }
426
}
427

            
428
#[must_use] pub fn format_nth_child_selector(n: &CssNthChildSelector) -> String {
429
    match n {
430
        CssNthChildSelector::Number(num) => format!("CssNthChildSelector::Number({num})"),
431
        CssNthChildSelector::Even => "CssNthChildSelector::Even".to_string(),
432
        CssNthChildSelector::Odd => "CssNthChildSelector::Odd".to_string(),
433
        CssNthChildSelector::Pattern(CssNthChildPattern {
434
            pattern_repeat,
435
            offset,
436
        }) => format!(
437
            "CssNthChildSelector::Pattern(CssNthChildPattern {{ pattern_repeat: {pattern_repeat}, offset: {offset} }})"
438
        ),
439
    }
440
}
441

            
442
#[must_use] pub fn print_declaration(decl: &CssDeclaration, tabs: usize) -> String {
443
    match decl {
444
        CssDeclaration::Static(s) => format!(
445
            "CssDeclaration::Static({})",
446
            format_static_css_prop(s, tabs)
447
        ),
448
        CssDeclaration::Dynamic(d) => format!(
449
            "CssDeclaration::Dynamic({})",
450
            format_dynamic_css_prop(d, tabs)
451
        ),
452
    }
453
}
454

            
455
#[must_use] pub fn format_dynamic_css_prop(decl: &DynamicCssProperty, tabs: usize) -> String {
456
    let t = String::from("    ").repeat(tabs);
457
    format!(
458
        "DynamicCssProperty {{\r\n{}    dynamic_id: {:?},\r\n{}    default_value: {},\r\n{}}}",
459
        t,
460
        decl.dynamic_id,
461
        t,
462
        format_static_css_prop(&decl.default_value, tabs + 1),
463
        t
464
    )
465
}
466

            
467
#[cfg(test)]
468
mod tests {
469
    use alloc::vec;
470

            
471
    use super::*;
472
    use crate::css::CssRuleBlock;
473

            
474
2
    fn sample_css() -> Css {
475
2
        let path = CssPath::new(vec![CssPathSelector::Type(NodeTypeTag::Div)]);
476
2
        let block = CssRuleBlock::new(path, vec![]);
477
2
        Css {
478
2
            rules: vec![block].into(),
479
2
            keyframes: crate::css::KeyframesVec::from_const_slice(&[]),
480
2
        }
481
2
    }
482

            
483
    #[test]
484
1
    fn rust_backend_emits_const_literal() {
485
1
        let css = sample_css();
486
1
        let rust = RustBackend.emit_css(&css);
487
1
        assert!(rust.contains("const CSS: Css"));
488
1
        assert!(rust.contains("NodeTypeTag::Div"));
489
1
    }
490

            
491
    #[test]
492
1
    fn rust_backend_emits_project_files() {
493
1
        let css = sample_css();
494
1
        let files = RustBackend.emit_project(&css);
495
2
        let paths: Vec<_> = files.iter().map(|f| f.path.as_str()).collect();
496
1
        assert!(paths.contains(&"Cargo.toml"));
497
1
        assert!(paths.contains(&"src/main.rs"));
498
1
        let main_rs = files
499
1
            .iter()
500
2
            .find(|f| f.path == "src/main.rs")
501
1
            .expect("main.rs missing");
502
1
        assert!(main_rs.contains_const_literal());
503
1
    }
504

            
505
    impl GeneratedFile {
506
1
        fn contains_const_literal(&self) -> bool {
507
1
            self.contents.contains("const CSS: Css")
508
1
        }
509
    }
510
}