tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definitions

⎇ 9e53f03adea2b770d68954e404d2fa407dfc7d40 scripts/gentest/src/main.rs Rust · 657 lines · 23.8 KB History Who touched what raw

Implement "gap" property for flexbox algorithm (#248) 9e53f03ad · Nico Burns ·

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use convert_case::{Case, Casing};
6use fantoccini::{Client, ClientBuilder};
7
8use log::*;
9use proc_macro2::{Span, TokenStream};
10use quote::{format_ident, quote};
11use syn::Ident;
12
13#[tokio::main]
14async fn main() {
15    env_logger::init();
16    // this requires being run by cargo, which is iffy
17    let root_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
18    let repo_root = root_dir.parent().and_then(Path::parent).unwrap();
19
20    let fixtures_root = repo_root.join("test_fixtures");
21    let fixtures = fs::read_dir(fixtures_root).unwrap();
22
23    info!("reading test fixtures from disk");
24    let mut fixtures: Vec<_> = fixtures
25        .into_iter()
26        .filter_map(|a| a.ok())
27        .filter(|f| f.path().is_file() && f.path().extension().map(|p| p == "html").unwrap_or(false))
28        .map(|f| {
29            let fixture_path = f.path();
30            let name = fixture_path.file_stem().unwrap().to_str().unwrap().to_string();
31            (name, fixture_path)
32        })
33        .collect();
34    fixtures.sort_unstable_by_key(|f| f.0.clone());
35
36    info!("starting webdriver instance");
37    let webdriver_url = "http://localhost:4444";
38    let mut webdriver_handle = Command::new("chromedriver")
39        .arg("--port=4444")
40        .spawn()
41        .expect("ChromeDriver not found: Make sure you have it installed and added to your PATH.");
42
43    // this is silly, but it works
44    std::thread::sleep(std::time::Duration::from_secs(1));
45
46    let mut caps = serde_json::map::Map::new();
47    let chrome_opts = serde_json::json!({ "args": ["--headless", "--disable-gpu"] });
48    caps.insert("goog:chromeOptions".to_string(), chrome_opts.clone());
49
50    info!("spawning webdriver client and collecting test descriptions");
51    let client = ClientBuilder::native().capabilities(caps.clone()).connect(webdriver_url).await.unwrap();
52
53    let mut test_descs = vec![];
54    for (name, fixture_path) in fixtures {
55        test_descs.push(test_root_element(client.clone(), name, fixture_path).await);
56    }
57
58    info!("killing webdriver instance...");
59    webdriver_handle.kill().unwrap();
60
61    info!("generating test sources and concatenating...");
62
63    let bench_descs: Vec<_> = test_descs
64        .iter()
65        .map(|(name, description)| {
66            debug!("generating bench contents for {}", &name);
67            (name.clone(), generate_bench(description))
68        })
69        .collect();
70
71    let test_descs: Vec<_> = test_descs
72        .iter()
73        .map(|(name, description)| {
74            debug!("generating test contents for {}", &name);
75            (name.clone(), generate_test(name, description))
76        })
77        .collect();
78
79    let benchmarks: Vec<_> = test_descs
80        .iter()
81        .map(|(name, _)| {
82            let bench_mod = Ident::new(name, Span::call_site());
83            quote!(#bench_mod::compute())
84        })
85        .collect();
86
87    let test_mods = test_descs
88        .iter()
89        .map(|(name, _)| {
90            let name = Ident::new(name, Span::call_site());
91            quote!(mod #name;)
92        })
93        .fold(quote!(), |a, b| quote!(#a #b));
94
95    let bench_mods = quote!(
96        use criterion::{criterion_group, criterion_main, Criterion};
97
98        #test_mods
99
100        fn benchmark(c: &mut Criterion) {
101            c.bench_function("generated benchmarks", |b| {
102                b.iter(|| { #(#benchmarks;)* })
103            });
104        }
105
106        criterion_group!(benches, benchmark);
107        criterion_main!(benches);
108    );
109
110    info!("writing generated benchmarks file to disk...");
111    let benches_base_path = repo_root.join("benches").join("generated");
112    fs::remove_dir_all(&benches_base_path).unwrap();
113    fs::create_dir(&benches_base_path).unwrap();
114    for (name, bench_body) in bench_descs {
115        let mut bench_filename = benches_base_path.join(&name);
116        bench_filename.set_extension("rs");
117        debug!("writing {} to disk...", &name);
118        fs::write(bench_filename, bench_body.to_string()).unwrap();
119    }
120    fs::write(benches_base_path.join("mod.rs"), bench_mods.to_string()).unwrap();
121
122    info!("writing generated test file to disk...");
123    let tests_base_path = repo_root.join("tests").join("generated");
124    fs::remove_dir_all(&tests_base_path).unwrap();
125    fs::create_dir(&tests_base_path).unwrap();
126    for (name, test_body) in test_descs {
127        let mut test_filename = tests_base_path.join(&name);
128        test_filename.set_extension("rs");
129        debug!("writing {} to disk...", &name);
130        fs::write(test_filename, test_body.to_string()).unwrap();
131    }
132    fs::write(tests_base_path.join("mod.rs"), test_mods.to_string()).unwrap();
133
134    info!("formatting the source directory");
135    Command::new("cargo").arg("fmt").current_dir(repo_root).status().unwrap();
136}
137
138async fn test_root_element(client: Client, name: String, fixture_path: impl AsRef<Path>) -> (String, json::JsonValue) {
139    let fixture_path = fixture_path.as_ref();
140
141    let url = format!("file://{}", fixture_path.display());
142
143    client.goto(&url).await.unwrap();
144    let description = client
145        .execute("return JSON.stringify(describeElement(document.getElementById('test-root')))", vec![])
146        .await
147        .unwrap();
148    let description_string = description.as_str().unwrap();
149    let description = json::parse(description_string).unwrap();
150    (name, description)
151}
152
153fn generate_bench(description: &json::JsonValue) -> TokenStream {
154    let node_description = generate_node("node", description);
155
156    quote!(
157        pub fn compute() {
158            let mut taffy = taffy::Taffy::new();
159            #node_description
160            taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
161        }
162    )
163}
164
165fn generate_test(name: impl AsRef<str>, description: &json::JsonValue) -> TokenStream {
166    let name = name.as_ref();
167    let name = Ident::new(name, Span::call_site());
168    let node_description = generate_node("node", description);
169    let assertions = generate_assertions("node", description);
170
171    quote!(
172        #[test]
173        fn #name() {
174            let mut taffy = taffy::Taffy::new();
175            #node_description
176            taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
177            #assertions
178        }
179    )
180}
181
182fn generate_assertions(ident: &str, node: &json::JsonValue) -> TokenStream {
183    let layout = &node["layout"];
184
185    let read_f32 = |s: &str| layout[s].as_f32().unwrap();
186    let width = read_f32("width");
187    let height = read_f32("height");
188    let x = read_f32("x");
189    let y = read_f32("y");
190
191    let children = {
192        let mut c = Vec::new();
193        if let json::JsonValue::Array(ref value) = node["children"] {
194            for (idx, child) in value.iter().enumerate() {
195                c.push(generate_assertions(&format!("{}{}", ident, idx), child));
196            }
197        };
198        c.into_iter().fold(quote!(), |a, b| quote!(#a #b))
199    };
200
201    let ident = Ident::new(ident, Span::call_site());
202
203    quote!(
204        assert_eq!(taffy.layout(#ident).unwrap().size.width, #width);
205        assert_eq!(taffy.layout(#ident).unwrap().size.height, #height);
206        assert_eq!(taffy.layout(#ident).unwrap().location.x, #x);
207        assert_eq!(taffy.layout(#ident).unwrap().location.y, #y);
208
209        #children
210    )
211}
212
213fn generate_node(ident: &str, node: &json::JsonValue) -> TokenStream {
214    let style = &node["style"];
215
216    fn quote_object_prop(
217        prop_name: &str,
218        style: &json::JsonValue,
219        quoter: impl Fn(&json::object::Object) -> TokenStream,
220    ) -> TokenStream {
221        let prop_name_snake_case = prop_name.to_case(Case::Snake);
222        let prop_name_camel_case = prop_name.to_case(Case::Camel);
223        let prop_name_ident = format_ident!("{}", prop_name_snake_case);
224        match style[prop_name_camel_case] {
225            json::JsonValue::Object(ref value) => {
226                let prop_value = quoter(value);
227                quote!(#prop_name_ident: #prop_value,)
228            }
229            _ => quote!(),
230        }
231    }
232
233    fn quote_array_prop(
234        prop_name: &str,
235        style: &json::JsonValue,
236        quoter: impl Fn(&[json::JsonValue]) -> TokenStream,
237    ) -> TokenStream {
238        let prop_name_snake_case = prop_name.to_case(Case::Snake);
239        let prop_name_camel_case = prop_name.to_case(Case::Camel);
240        let prop_name_ident = format_ident!("{}", prop_name_snake_case);
241        match style[prop_name_camel_case] {
242            json::JsonValue::Array(ref value) => {
243                let prop_value = quoter(value);
244                quote!(#prop_name_ident: #prop_value,)
245            }
246            _ => quote!(),
247        }
248    }
249
250    // This is currently unused, but leaving for future use.
251    #[allow(dead_code)]
252    fn quote_string_prop(
253        prop_name: &str,
254        style: &json::JsonValue,
255        quoter: impl Fn(&str) -> TokenStream,
256    ) -> TokenStream {
257        let prop_name_snake_case = prop_name.to_case(Case::Snake);
258        let prop_name_camel_case = prop_name.to_case(Case::Camel);
259        let prop_name_ident = format_ident!("{}", prop_name_snake_case);
260        match style[prop_name_camel_case] {
261            json::JsonValue::Short(ref value) => {
262                let prop_value = quoter(value.as_ref());
263                quote!(#prop_name_ident: #prop_value,)
264            }
265            json::JsonValue::String(ref value) => {
266                let prop_value = quoter(value.as_ref());
267                quote!(#prop_name_ident: #prop_value,)
268            }
269            _ => quote!(),
270        }
271    }
272
273    fn quote_number_prop<T: From<json::number::Number>>(
274        prop_name: &str,
275        style: &json::JsonValue,
276        quoter: impl Fn(T) -> TokenStream,
277    ) -> TokenStream {
278        let prop_name_snake_case = prop_name.to_case(Case::Snake);
279        let prop_name_camel_case = prop_name.to_case(Case::Camel);
280        let prop_name_ident = format_ident!("{}", prop_name_snake_case);
281        match style[prop_name_camel_case] {
282            json::JsonValue::Number(ref value) => {
283                let prop_value = quoter((*value).into());
284                quote!(#prop_name_ident: #prop_value,)
285            }
286            _ => quote!(),
287        }
288    }
289
290    let display = match style["display"] {
291        json::JsonValue::Short(ref value) => match value.as_ref() {
292            "none" => quote!(display: taffy::style::Display::None,),
293            "grid" => quote!(display: taffy::style::Display::Grid,),
294            _ => quote!(display: taffy::style::Display::Flex,),
295        },
296        _ => quote!(),
297    };
298
299    let position_type = match style["positionType"] {
300        json::JsonValue::Short(ref value) => match value.as_ref() {
301            "absolute" => quote!(position_type: taffy::style::PositionType::Absolute,),
302            _ => quote!(),
303        },
304        _ => quote!(),
305    };
306
307    let direction = match style["direction"] {
308        json::JsonValue::Short(ref value) => match value.as_ref() {
309            "rtl" => quote!(direction: taffy::style::Direction::RTL,),
310            "ltr" => quote!(direction: taffy::style::Direction::LTR,),
311            _ => quote!(),
312        },
313        _ => quote!(),
314    };
315
316    let flex_direction = match style["flexDirection"] {
317        json::JsonValue::Short(ref value) => match value.as_ref() {
318            "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,),
319            "column" => quote!(flex_direction: taffy::style::FlexDirection::Column,),
320            "column-reverse" => quote!(flex_direction: taffy::style::FlexDirection::ColumnReverse,),
321            _ => quote!(),
322        },
323        _ => quote!(),
324    };
325
326    let flex_wrap = match style["flexWrap"] {
327        json::JsonValue::Short(ref value) => match value.as_ref() {
328            "wrap" => quote!(flex_wrap: taffy::style::FlexWrap::Wrap,),
329            "wrap-reverse" => quote!(flex_wrap: taffy::style::FlexWrap::WrapReverse,),
330            _ => quote!(),
331        },
332        _ => quote!(),
333    };
334
335    let overflow = match style["overflow"] {
336        json::JsonValue::Short(ref value) => match value.as_ref() {
337            "hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
338            "scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
339            _ => quote!(),
340        },
341        _ => quote!(),
342    };
343
344    let align_items = match style["alignItems"] {
345        json::JsonValue::Short(ref value) => match value.as_ref() {
346            "flex-start" => quote!(align_items: taffy::style::AlignItems::FlexStart,),
347            "flex-end" => quote!(align_items: taffy::style::AlignItems::FlexEnd,),
348            "center" => quote!(align_items: taffy::style::AlignItems::Center,),
349            "baseline" => quote!(align_items: taffy::style::AlignItems::Baseline,),
350            _ => quote!(),
351        },
352        _ => quote!(),
353    };
354
355    let align_self = match style["alignSelf"] {
356        json::JsonValue::Short(ref value) => match value.as_ref() {
357            "flex-start" => quote!(align_self: taffy::style::AlignSelf::FlexStart,),
358            "flex-end" => quote!(align_self: taffy::style::AlignSelf::FlexEnd,),
359            "center" => quote!(align_self: taffy::style::AlignSelf::Center,),
360            "baseline" => quote!(align_self: taffy::style::AlignSelf::Baseline,),
361            "stretch" => quote!(align_self: taffy::style::AlignSelf::Stretch,),
362            _ => quote!(),
363        },
364        _ => quote!(),
365    };
366
367    let align_content = match style["alignContent"] {
368        json::JsonValue::Short(ref value) => match value.as_ref() {
369            "flex-start" => quote!(align_content: taffy::style::AlignContent::FlexStart,),
370            "flex-end" => quote!(align_content: taffy::style::AlignContent::FlexEnd,),
371            "center" => quote!(align_content: taffy::style::AlignContent::Center,),
372            "space-between" => quote!(align_content: taffy::style::AlignContent::SpaceBetween,),
373            "space-around" => quote!(align_content: taffy::style::AlignContent::SpaceAround,),
374            "space-evenly" => quote!(align_content: taffy::style::AlignContent::SpaceEvenly,),
375            _ => quote!(),
376        },
377        _ => quote!(),
378    };
379
380    let justify_content = match style["justifyContent"] {
381        json::JsonValue::Short(ref value) => match value.as_ref() {
382            "flex-end" => quote!(justify_content: taffy::style::JustifyContent::FlexEnd,),
383            "center" => quote!(justify_content: taffy::style::JustifyContent::Center,),
384            "space-between" => quote!(justify_content: taffy::style::JustifyContent::SpaceBetween,),
385            "space-around" => quote!(justify_content: taffy::style::JustifyContent::SpaceAround,),
386            "space-evenly" => quote!(justify_content: taffy::style::JustifyContent::SpaceEvenly,),
387            _ => quote!(),
388        },
389        _ => quote!(),
390    };
391
392    let flex_grow = quote_number_prop("flex_grow", style, |value: f32| quote!(#value));
393    let flex_shrink = quote_number_prop("flex_shrink", style, |value: f32| quote!(#value));
394
395    let flex_basis = quote_object_prop("flex_basis", style, generate_dimension);
396    let size = quote_object_prop("size", style, generate_size);
397    let min_size = quote_object_prop("min_size", style, generate_size);
398    let max_size = quote_object_prop("max_size", style, generate_size);
399
400    let gap = quote_object_prop("gap", style, generate_gap);
401
402    let grid_template_rows = quote_array_prop("grid_template_rows", style, generate_track_definition_list);
403    let grid_template_columns = quote_array_prop("grid_template_columns", style, generate_track_definition_list);
404    let grid_auto_rows = quote_array_prop("grid_auto_rows", style, generate_track_definition_list);
405    let grid_auto_columns = quote_array_prop("grid_auto_columns", style, generate_track_definition_list);
406    let grid_auto_flow = quote_object_prop("grid_auto_flow", style, generate_grid_auto_flow);
407
408    let grid_row_start = quote_object_prop("grid_row_start", style, generate_grid_position);
409    let grid_row_end = quote_object_prop("grid_row_end", style, generate_grid_position);
410    let grid_column_start = quote_object_prop("grid_column_start", style, generate_grid_position);
411    let grid_column_end = quote_object_prop("grid_column_end", style, generate_grid_position);
412
413    macro_rules! edges_quoted {
414        ($style:ident, $val:ident) => {
415            let $val = match $style[stringify!($val)] {
416                json::JsonValue::Object(ref value) => {
417                    let edges = generate_edges(value);
418                    quote!($val: #edges,)
419                },
420                _ => quote!(),
421            };
422        };
423    }
424
425    edges_quoted!(style, margin);
426    edges_quoted!(style, padding);
427    edges_quoted!(style, position);
428    edges_quoted!(style, border);
429
430    let (children_body, children) = match node["children"] {
431        json::JsonValue::Array(ref value) => {
432            if !value.is_empty() {
433                let body = value
434                    .iter()
435                    .enumerate()
436                    .map(|(i, child)| generate_node(&format!("{}{}", ident, i), child))
437                    .collect();
438                let idents = value
439                    .iter()
440                    .enumerate()
441                    .map(|(i, _)| Ident::new(&format!("{}{}", ident, i), Span::call_site()))
442                    .collect::<Vec<_>>();
443                (body, quote!(&[#(#idents),*]))
444            } else {
445                (quote!(), quote!(&[]))
446            }
447        }
448        _ => (quote!(), quote!()),
449    };
450
451    let ident = Ident::new(ident, Span::call_site());
452
453    quote!(
454        #children_body
455        let #ident = taffy.new_with_children(
456        taffy::style::FlexboxLayout {
457            #display
458            #direction
459            #position_type
460            #flex_direction
461            #flex_wrap
462            #overflow
463            #align_items
464            #align_self
465            #align_content
466            #justify_content
467            #flex_grow
468            #flex_shrink
469            #flex_basis
470            #gap
471            #grid_template_rows
472            #grid_template_columns
473            #grid_auto_rows
474            #grid_auto_columns
475            #grid_auto_flow
476            #grid_row_start
477            #grid_row_end
478            #grid_column_start
479            #grid_column_end
480            #size
481            #min_size
482            #max_size
483            #margin
484            #padding
485            #position
486            #border
487            ..Default::default()
488        },
489        #children
490        // TODO: Only add children if they exist
491    ).unwrap();)
492}
493
494macro_rules! dim_quoted_renamed {
495    ($obj:ident, $in_name:ident, $out_name:ident) => {
496        let $out_name = match $obj.get(stringify!($in_name)) {
497            Some(json::JsonValue::Object(ref value)) => {
498                let dim = generate_dimension(value);
499                quote!($out_name: #dim,)
500            }
501            _ => quote!(),
502        };
503    };
504}
505
506macro_rules! dim_quoted {
507    ($obj:ident, $dim_name:ident) => {
508        dim_quoted_renamed!($obj, $dim_name, $dim_name)
509    };
510}
511
512fn generate_size(size: &json::object::Object) -> TokenStream {
513    dim_quoted!(size, width);
514    dim_quoted!(size, height);
515    quote!(
516        taffy::geometry::Size {
517            #width #height
518            ..Default::default()
519        }
520    )
521}
522
523fn generate_gap(size: &json::object::Object) -> TokenStream {
524    dim_quoted_renamed!(size, column, width);
525    dim_quoted_renamed!(size, row, height);
526    quote!(
527        taffy::geometry::Size {
528            #width #height
529            ..Default::default()
530        }
531    )
532}
533
534fn generate_dimension(dimen: &json::object::Object) -> TokenStream {
535    let unit = dimen.get("unit").unwrap();
536    let value = || dimen.get("value").unwrap().as_f32().unwrap();
537
538    match unit {
539        json::JsonValue::Short(ref unit) => match unit.as_ref() {
540            "auto" => quote!(taffy::style::Dimension::Auto),
541            "points" => {
542                let value = value();
543                quote!(taffy::style::Dimension::Points(#value))
544            }
545            "percent" => {
546                let value = value();
547                quote!(taffy::style::Dimension::Percent(#value))
548            }
549            _ => unreachable!(),
550        },
551        _ => unreachable!(),
552    }
553}
554
555fn generate_edges(dimen: &json::object::Object) -> TokenStream {
556    dim_quoted!(dimen, start);
557    dim_quoted!(dimen, end);
558    dim_quoted!(dimen, top);
559    dim_quoted!(dimen, bottom);
560
561    quote!(taffy::geometry::Rect {
562        #start #end #top #bottom
563        ..Default::default()
564    })
565}
566
567fn generate_grid_auto_flow(auto_flow: &json::object::Object) -> TokenStream {
568    let direction = auto_flow.get("direction").unwrap().as_str().unwrap();
569    let algorithm = auto_flow.get("algorithm").unwrap().as_str().unwrap();
570
571    match (direction, algorithm) {
572        ("row", "sparse") => quote!(taffy::style::GridAutoFlow::Row),
573        ("column", "sparse") => quote!(taffy::style::GridAutoFlow::Column),
574        ("row", "dense") => quote!(taffy::style::GridAutoFlow::RowDense),
575        ("column", "dense") => quote!(taffy::style::GridAutoFlow::ColumnDense),
576        _ => unreachable!(),
577    }
578}
579
580fn generate_grid_position(grid_position: &json::object::Object) -> TokenStream {
581    let kind = grid_position.get("kind").unwrap();
582    let value = || grid_position.get("value").unwrap().as_f32().unwrap();
583
584    match kind {
585        json::JsonValue::Short(ref kind) => match kind.as_ref() {
586            "auto" => quote!(taffy::style::GridLine::Auto),
587            "span" => {
588                let value = value() as u16;
589                quote!(taffy::style::GridLine::Span(#value))
590            }
591            "track" => {
592                let value = value() as i16;
593                quote!(taffy::style::GridLine::Track(#value))
594            }
595            _ => unreachable!(),
596        },
597        _ => unreachable!(),
598    }
599}
600
601fn generate_track_definition_list(raw_list: &[json::JsonValue]) -> TokenStream {
602    let list = raw_list.iter().map(|obj| match obj {
603        json::JsonValue::Object(inner) => generate_track_definition(inner),
604        _ => unreachable!(),
605    });
606
607    quote!(vec![#(#list),*])
608}
609
610fn generate_track_definition(track_definition: &json::object::Object) -> TokenStream {
611    let kind = track_definition.get("kind").unwrap().as_str().unwrap();
612    let name = || track_definition.get("name").unwrap().as_str().unwrap();
613    let arguments = || track_definition.get("arguments").unwrap();
614
615    match kind {
616        "scalar" => generate_scalar_definition(track_definition),
617        "function" => match (name(), arguments()) {
618            ("minmax", json::JsonValue::Array(arguments)) => {
619                if arguments.len() != 2 {
620                    panic!("minmax function with the wrong number of arguments");
621                }
622                let min = match arguments[0] {
623                    json::JsonValue::Object(ref arg) => generate_scalar_definition(arg),
624                    _ => unreachable!(),
625                };
626                let max = match arguments[1] {
627                    json::JsonValue::Object(ref arg) => generate_scalar_definition(arg),
628                    _ => unreachable!(),
629                };
630                quote!(taffy::style::GridTrackSizingFunction::MinMax{ min: #min, max: #max })
631            }
632            // TODO: Add support for fit-content
633            _ => unreachable!(),
634        },
635        _ => unreachable!(),
636    }
637}
638
639fn generate_scalar_definition(track_definition: &json::object::Object) -> TokenStream {
640    let unit = || track_definition.get("unit").unwrap().as_str().unwrap();
641    let value = || track_definition.get("value").unwrap().as_f32().unwrap();
642
643    match unit() {
644        "auto" => quote!(taffy::style::GridTrackSizingFunction::Auto),
645        "min-content" => quote!(taffy::style::GridTrackSizingFunction::MinContent),
646        "max-content" => quote!(taffy::style::GridTrackSizingFunction::MaxContent),
647        "points" | "percent" => {
648            let value = generate_dimension(track_definition);
649            quote!(taffy::style::GridTrackSizingFunction::Fixed(#value))
650        }
651        "fraction" => {
652            let value: f32 = value();
653            quote!(taffy::style::GridTrackSizingFunction::Flex(#value))
654        }
655        _ => unreachable!(),
656    }
657}