⎇ 6652882a96948b4d8ee433a176d428cf3c3916bf
scripts/gentest/src/main.rs
Historia
Quién tocó qué
crudo
Convert measure tests to gentests (#292)
1use std::fs;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use convert_case::{Case, Casing};
6use fantoccini::{Client, ClientBuilder};
7use log::*;
8use proc_macro2::{Span, TokenStream};
9use quote::{format_ident, quote};
10use serde_json::Value;
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.file_name().to_string_lossy().starts_with('x')) // ignore tests beginning with x
28 .filter(|f| f.path().is_file() && f.path().extension().map(|p| p == "html").unwrap_or(false))
29 .map(|f| {
30 let fixture_path = f.path();
31 let name = fixture_path.file_stem().unwrap().to_str().unwrap().to_string();
32 (name, fixture_path)
33 })
34 .collect();
35 fixtures.sort_unstable_by_key(|f| f.0.clone());
36
37 info!("starting webdriver instance");
38 let webdriver_url = "http://localhost:4444";
39 let mut webdriver_handle = Command::new("chromedriver")
40 .arg("--port=4444")
41 .spawn()
42 .expect("ChromeDriver not found: Make sure you have it installed and added to your PATH.");
43
44 // this is silly, but it works
45 std::thread::sleep(std::time::Duration::from_secs(1));
46
47 let mut caps = serde_json::map::Map::new();
48 let chrome_opts = serde_json::json!({ "args": ["--headless", "--disable-gpu"] });
49 caps.insert("goog:chromeOptions".to_string(), chrome_opts.clone());
50
51 info!("spawning webdriver client and collecting test descriptions");
52 let client = ClientBuilder::native().capabilities(caps.clone()).connect(webdriver_url).await.unwrap();
53
54 let mut test_descs = vec![];
55 for (name, fixture_path) in fixtures {
56 test_descs.push(test_root_element(client.clone(), name, fixture_path).await);
57 }
58
59 info!("killing webdriver instance...");
60 webdriver_handle.kill().unwrap();
61
62 info!("generating test sources and concatenating...");
63
64 let bench_descs: Vec<_> = test_descs
65 .iter()
66 .map(|(name, description)| {
67 debug!("generating bench contents for {}", &name);
68 (name.clone(), generate_bench(description))
69 })
70 .collect();
71
72 let test_descs: Vec<_> = test_descs
73 .iter()
74 .map(|(name, description)| {
75 debug!("generating test contents for {}", &name);
76 (name.clone(), generate_test(name, description))
77 })
78 .collect();
79
80 let benchmarks: Vec<_> = test_descs
81 .iter()
82 .map(|(name, _)| {
83 let bench_mod = Ident::new(name, Span::call_site());
84 if name.starts_with("grid") {
85 quote!(#[cfg(feature = "experimental_grid")] #bench_mod::compute())
86 } else {
87 quote!(#bench_mod::compute())
88 }
89 })
90 .collect();
91
92 let mod_statemnts = test_descs
93 .iter()
94 .map(|(name, _)| {
95 let name_ident = Ident::new(name, Span::call_site());
96 if name.starts_with("grid") {
97 quote!(#[cfg(feature = "experimental_grid")] mod #name_ident;)
98 } else {
99 quote!(mod #name_ident;)
100 }
101 })
102 .fold(quote!(), |a, b| quote!(#a #b));
103 let generic_measure_function = generate_generic_measure_function();
104
105 let test_mod_file = quote!(
106 #generic_measure_function
107 #mod_statemnts
108 );
109
110 let bench_mod_file = quote!(
111 use criterion::{criterion_group, criterion_main, Criterion};
112
113 #generic_measure_function
114
115 #mod_statemnts
116
117 fn benchmark(c: &mut Criterion) {
118 c.bench_function("generated benchmarks", |b| {
119 b.iter(|| { #(#benchmarks;)* })
120 });
121 }
122
123 criterion_group!(benches, benchmark);
124 criterion_main!(benches);
125 );
126
127 info!("writing generated benchmarks file to disk...");
128 let benches_base_path = repo_root.join("benches").join("generated");
129 fs::remove_dir_all(&benches_base_path).unwrap();
130 fs::create_dir(&benches_base_path).unwrap();
131 for (name, bench_body) in bench_descs {
132 let mut bench_filename = benches_base_path.join(&name);
133 bench_filename.set_extension("rs");
134 debug!("writing {} to disk...", &name);
135 fs::write(bench_filename, bench_body.to_string()).unwrap();
136 }
137 fs::write(benches_base_path.join("mod.rs"), bench_mod_file.to_string()).unwrap();
138
139 info!("writing generated test file to disk...");
140 let tests_base_path = repo_root.join("tests").join("generated");
141 fs::remove_dir_all(&tests_base_path).unwrap();
142 fs::create_dir(&tests_base_path).unwrap();
143 for (name, test_body) in test_descs {
144 let mut test_filename = tests_base_path.join(&name);
145 test_filename.set_extension("rs");
146 debug!("writing {} to disk...", &name);
147 fs::write(test_filename, test_body.to_string()).unwrap();
148 }
149 fs::write(tests_base_path.join("mod.rs"), test_mod_file.to_string()).unwrap();
150
151 info!("formatting the source directory");
152 Command::new("cargo").arg("fmt").current_dir(repo_root).status().unwrap();
153}
154
155async fn test_root_element(client: Client, name: String, fixture_path: impl AsRef<Path>) -> (String, Value) {
156 let fixture_path = fixture_path.as_ref();
157
158 let url = format!("file://{}", fixture_path.display());
159
160 client.goto(&url).await.unwrap();
161 let description = client
162 .execute("return JSON.stringify(describeElement(document.getElementById('test-root')))", vec![])
163 .await
164 .unwrap();
165 let description_string = description.as_str().unwrap();
166 let description = serde_json::from_str(description_string).unwrap();
167 (name, description)
168}
169
170fn generate_bench(description: &Value) -> TokenStream {
171 let node_description = generate_node("node", description);
172
173 quote!(
174 pub fn compute() {
175 #[allow(unused_imports)]
176 use taffy::prelude::*;
177 let mut taffy = taffy::Taffy::new();
178 #node_description
179 taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
180 }
181 )
182}
183
184fn generate_test(name: impl AsRef<str>, description: &Value) -> TokenStream {
185 let name = name.as_ref();
186 let name = Ident::new(name, Span::call_site());
187 let node_description = generate_node("node", description);
188 let assertions = generate_assertions("node", description);
189
190 quote!(
191 #[test]
192 fn #name() {
193 #[allow(unused_imports)]
194 use taffy::{layout::Layout, prelude::*};
195 use slotmap::Key;
196 let mut taffy = taffy::Taffy::new();
197 #node_description
198 taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
199
200 println!("\nComputed tree:");
201 taffy::debug::print_tree(&taffy, node);
202 println!();
203
204 #assertions
205 }
206 )
207}
208
209fn generate_assertions(ident: &str, node: &Value) -> TokenStream {
210 let layout = &node["layout"];
211
212 let read_f32 = |s: &str| layout[s].as_f64().unwrap() as f32;
213 let width = read_f32("width");
214 let height = read_f32("height");
215 let x = read_f32("x");
216 let y = read_f32("y");
217
218 let children = {
219 let mut c = Vec::new();
220 if let Value::Array(ref value) = node["children"] {
221 for (idx, child) in value.iter().enumerate() {
222 c.push(generate_assertions(&format!("{ident}{idx}"), child));
223 }
224 };
225 c.into_iter().fold(quote!(), |a, b| quote!(#a #b))
226 };
227
228 let ident = Ident::new(ident, Span::call_site());
229
230 quote!(
231 let Layout { size, location, .. } = taffy.layout(#ident).unwrap();
232 assert_eq!(size.width, #width, "width of node {:?}. Expected {}. Actual {}", #ident.data(), #width, size.width);
233 assert_eq!(size.height, #height, "height of node {:?}. Expected {}. Actual {}", #ident.data(), #height, size.height);
234 assert_eq!(location.x, #x, "x of node {:?}. Expected {}. Actual {}", #ident.data(), #x, location.x);
235 assert_eq!(location.y, #y, "y of node {:?}. Expected {}. Actual {}", #ident.data(), #y, location.y);
236
237 #children
238 )
239}
240
241macro_rules! dim_quoted_renamed {
242 ($obj:ident, $in_name:ident, $out_name:ident, $value_mapper:ident, $default:expr) => {
243 let $out_name = match $obj.get(stringify!($in_name)) {
244 Some(Value::Object(ref value)) => {
245 let dim = $value_mapper(value);
246 quote!($out_name: #dim,)
247 }
248 _ => {
249 let dim = $default;
250 quote!($out_name: #dim,)
251 }
252 };
253 };
254}
255
256macro_rules! dim_quoted {
257 ($obj:ident, $dim_name:ident, $value_mapper: ident, $default:expr) => {
258 dim_quoted_renamed!($obj, $dim_name, $dim_name, $value_mapper, $default)
259 };
260}
261
262macro_rules! edges_quoted {
263 ($style:ident, $val:ident, $value_mapper:ident, $default_value: expr) => {
264 let $val = match $style[stringify!($val)] {
265 Value::Object(ref value) => {
266 dim_quoted!(value, left, $value_mapper, $default_value);
267 dim_quoted!(value, right, $value_mapper, $default_value);
268 dim_quoted!(value, top, $value_mapper, $default_value);
269 dim_quoted!(value, bottom, $value_mapper, $default_value);
270
271 let edges = quote!(taffy::geometry::Rect {
272 #left #right #top #bottom
273 });
274
275 quote!($val: #edges,)
276 },
277 _ => quote!(),
278 };
279 };
280}
281
282fn generate_node(ident: &str, node: &Value) -> TokenStream {
283 let style = &node["style"];
284
285 fn snake_case_ident(ident_name: &str) -> Ident {
286 let name_snake_case = ident_name.to_case(Case::Snake);
287 format_ident!("{}", name_snake_case)
288 }
289
290 fn quote_object_value(
291 prop_name: &str,
292 style: &Value,
293 quoter: impl Fn(&serde_json::Map<String, Value>) -> TokenStream,
294 ) -> Option<TokenStream> {
295 match style[prop_name.to_case(Case::Camel)] {
296 Value::Object(ref value) => Some(quoter(value)),
297 _ => None,
298 }
299 }
300
301 fn quote_prop(prop_name: &str, value: TokenStream) -> TokenStream {
302 let ident = snake_case_ident(prop_name);
303 quote!(#ident: #value,)
304 }
305
306 fn quote_object_prop(
307 prop_name: &str,
308 style: &Value,
309 quoter: impl Fn(&serde_json::Map<String, Value>) -> TokenStream,
310 ) -> TokenStream {
311 match quote_object_value(prop_name, style, quoter) {
312 Some(prop_value) => quote_prop(prop_name, prop_value),
313 None => quote!(),
314 }
315 }
316
317 fn quote_array_prop(prop_name: &str, style: &Value, quoter: impl Fn(&[Value]) -> TokenStream) -> TokenStream {
318 let prop_name_snake_case = prop_name.to_case(Case::Snake);
319 let prop_name_camel_case = prop_name.to_case(Case::Camel);
320 let prop_name_ident = format_ident!("{}", prop_name_snake_case);
321 match style[prop_name_camel_case] {
322 Value::Array(ref value) => {
323 let prop_value = quoter(value);
324 quote!(#prop_name_ident: #prop_value,)
325 }
326 _ => quote!(),
327 }
328 }
329
330 fn get_string_value<'a, 'b, 'c: 'b>(prop_name: &'a str, style: &'c Value) -> Option<&'b str> {
331 match style[prop_name.to_case(Case::Camel)] {
332 Value::String(ref value) => Some(value),
333 _ => None,
334 }
335 }
336
337 fn quote_number_prop(prop_name: &str, style: &Value, quoter: impl Fn(f32) -> TokenStream) -> TokenStream {
338 let prop_name_snake_case = prop_name.to_case(Case::Snake);
339 let prop_name_camel_case = prop_name.to_case(Case::Camel);
340 let prop_name_ident = format_ident!("{}", prop_name_snake_case);
341 match style[prop_name_camel_case] {
342 Value::Number(ref value) => {
343 let prop_value = quoter(value.as_f64().unwrap() as f32);
344 quote!(#prop_name_ident: #prop_value,)
345 }
346 _ => quote!(),
347 }
348 }
349
350 let display = match style["display"] {
351 Value::String(ref value) => match value.as_ref() {
352 "none" => quote!(display: taffy::style::Display::None,),
353 "grid" => quote!(display: taffy::style::Display::Grid,),
354 _ => quote!(display: taffy::style::Display::Flex,),
355 },
356 _ => quote!(),
357 };
358
359 let position_type = match style["positionType"] {
360 Value::String(ref value) => match value.as_ref() {
361 "absolute" => quote!(position_type: taffy::style::PositionType::Absolute,),
362 _ => quote!(),
363 },
364 _ => quote!(),
365 };
366
367 let direction = match style["direction"] {
368 Value::String(ref value) => match value.as_ref() {
369 "rtl" => quote!(direction: taffy::style::Direction::RTL,),
370 "ltr" => quote!(direction: taffy::style::Direction::LTR,),
371 _ => quote!(),
372 },
373 _ => quote!(),
374 };
375
376 let flex_direction = match style["flexDirection"] {
377 Value::String(ref value) => match value.as_ref() {
378 "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,),
379 "column" => quote!(flex_direction: taffy::style::FlexDirection::Column,),
380 "column-reverse" => quote!(flex_direction: taffy::style::FlexDirection::ColumnReverse,),
381 _ => quote!(),
382 },
383 _ => quote!(),
384 };
385
386 let flex_wrap = match style["flexWrap"] {
387 Value::String(ref value) => match value.as_ref() {
388 "wrap" => quote!(flex_wrap: taffy::style::FlexWrap::Wrap,),
389 "wrap-reverse" => quote!(flex_wrap: taffy::style::FlexWrap::WrapReverse,),
390 _ => quote!(),
391 },
392 _ => quote!(),
393 };
394
395 let overflow = match style["overflow"] {
396 Value::String(ref value) => match value.as_ref() {
397 "hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
398 "scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
399 _ => quote!(),
400 },
401 _ => quote!(),
402 };
403
404 let align_items = match style["alignItems"] {
405 Value::String(ref value) => match value.as_ref() {
406 "flex-start" | "start" => quote!(align_items: Some(taffy::style::AlignItems::Start),),
407 "flex-end" | "end" => quote!(align_items: Some(taffy::style::AlignItems::End),),
408 "center" => quote!(align_items: Some(taffy::style::AlignItems::Center),),
409 "baseline" => quote!(align_items: Some(taffy::style::AlignItems::Baseline),),
410 "stretch" => quote!(align_items: Some(taffy::style::AlignItems::Stretch),),
411 _ => quote!(),
412 },
413 _ => quote!(),
414 };
415
416 let align_self = match style["alignSelf"] {
417 Value::String(ref value) => match value.as_ref() {
418 "flex-start" | "start" => quote!(align_self: Some(taffy::style::AlignSelf::Start),),
419 "flex-end" | "end" => quote!(align_self: Some(taffy::style::AlignSelf::End),),
420 "center" => quote!(align_self: Some(taffy::style::AlignSelf::Center),),
421 "baseline" => quote!(align_self: Some(taffy::style::AlignSelf::Baseline),),
422 "stretch" => quote!(align_self: Some(taffy::style::AlignSelf::Stretch),),
423 _ => quote!(),
424 },
425 _ => quote!(),
426 };
427
428 let justify_items = match style["justifyItems"] {
429 Value::String(ref value) => match value.as_ref() {
430 "flex-start" | "start" => quote!(justify_items: Some(taffy::style::JustifyItems::Start),),
431 "flex-end" | "end" => quote!(justify_items: Some(taffy::style::JustifyItems::End),),
432 "center" => quote!(justify_items: Some(taffy::style::JustifyItems::Center),),
433 "baseline" => quote!(justify_items: Some(taffy::style::JustifyItems::Baseline),),
434 "stretch" => quote!(justify_items: Some(taffy::style::JustifyItems::Stretch),),
435 _ => quote!(),
436 },
437 _ => quote!(),
438 };
439
440 let justify_self = match style["justifySelf"] {
441 Value::String(ref value) => match value.as_ref() {
442 "flex-start" | "start" => quote!(justify_self: Some(taffy::style::JustifySelf::Start),),
443 "flex-end" | "end" => quote!(justify_self: Some(taffy::style::JustifySelf::End),),
444 "center" => quote!(justify_self: Some(taffy::style::JustifySelf::Center),),
445 "baseline" => quote!(justify_self: Some(taffy::style::JustifySelf::Baseline),),
446 "stretch" => quote!(justify_self: Some(taffy::style::JustifySelf::Stretch),),
447 _ => quote!(),
448 },
449 _ => quote!(),
450 };
451
452 let align_content = match style["alignContent"] {
453 Value::String(ref value) => match value.as_ref() {
454 "flex-start" | "start" => quote!(align_content: Some(taffy::style::AlignContent::Start),),
455 "flex-end" | "end" => quote!(align_content: Some(taffy::style::AlignContent::End),),
456 "center" => quote!(align_content: Some(taffy::style::AlignContent::Center),),
457 "stretch" => quote!(align_content: Some(taffy::style::AlignContent::Stretch),),
458 "space-between" => quote!(align_content: Some(taffy::style::AlignContent::SpaceBetween),),
459 "space-around" => quote!(align_content: Some(taffy::style::AlignContent::SpaceAround),),
460 "space-evenly" => quote!(align_content: Some(taffy::style::AlignContent::SpaceEvenly),),
461 _ => quote!(),
462 },
463 _ => quote!(),
464 };
465
466 let justify_content = match style["justifyContent"] {
467 Value::String(ref value) => match value.as_ref() {
468 "flex-start" | "start" => quote!(justify_content: Some(taffy::style::JustifyContent::Start),),
469 "flex-end" | "end" => quote!(justify_content: Some(taffy::style::JustifyContent::End),),
470 "center" => quote!(justify_content: Some(taffy::style::JustifyContent::Center),),
471 "stretch" => quote!(align_content: Some(taffy::style::AlignContent::Stretch),),
472 "space-between" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceBetween),),
473 "space-around" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceAround),),
474 "space-evenly" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceEvenly),),
475 _ => quote!(),
476 },
477 _ => quote!(),
478 };
479
480 let flex_grow = quote_number_prop("flex_grow", style, |value: f32| quote!(#value));
481 let flex_shrink = quote_number_prop("flex_shrink", style, |value: f32| quote!(#value));
482
483 let flex_basis = quote_object_prop("flex_basis", style, generate_dimension);
484 let size = quote_object_prop("size", style, generate_size);
485 let min_size = quote_object_prop("min_size", style, generate_size);
486 let max_size = quote_object_prop("max_size", style, generate_size);
487
488 let gap = quote_object_prop("gap", style, generate_gap);
489
490 let grid_template_rows = quote_array_prop("grid_template_rows", style, generate_track_definition_list);
491 let grid_template_columns = quote_array_prop("grid_template_columns", style, generate_track_definition_list);
492 let grid_auto_rows = quote_array_prop("grid_auto_rows", style, generate_track_definition_list);
493 let grid_auto_columns = quote_array_prop("grid_auto_columns", style, generate_track_definition_list);
494 let grid_auto_flow = quote_object_prop("grid_auto_flow", style, generate_grid_auto_flow);
495
496 let default_grid_placement = quote!(taffy::style::GridPlacement::Auto);
497
498 let grid_row_start = quote_object_value("grid_row_start", style, generate_grid_position);
499 let grid_row_end = quote_object_value("grid_row_end", style, generate_grid_position);
500 let grid_row = if grid_row_start.is_some() || grid_row_end.is_some() {
501 quote_prop(
502 "grid_row",
503 generate_line(
504 grid_row_start.unwrap_or(default_grid_placement.clone()),
505 grid_row_end.unwrap_or(default_grid_placement.clone()),
506 ),
507 )
508 } else {
509 quote!()
510 };
511
512 let grid_column_start = quote_object_value("grid_column_start", style, generate_grid_position);
513 let grid_column_end = quote_object_value("grid_column_end", style, generate_grid_position);
514 let grid_column = if grid_column_start.is_some() || grid_column_end.is_some() {
515 quote_prop(
516 "grid_column",
517 generate_line(
518 grid_column_start.unwrap_or(default_grid_placement.clone()),
519 grid_column_end.unwrap_or(default_grid_placement),
520 ),
521 )
522 } else {
523 quote!()
524 };
525
526 let measure_func: Option<_> = get_string_value("text_content", node).map(generate_measure_function);
527
528 edges_quoted!(style, margin, generate_length_percentage_auto, quote!(zero()));
529 edges_quoted!(style, padding, generate_length_percentage, quote!(zero()));
530 edges_quoted!(style, border, generate_length_percentage, quote!(zero()));
531 edges_quoted!(style, position, generate_length_percentage_auto, quote!(auto()));
532
533 // Quote children
534 let child_descriptions: Vec<Value> = match node["children"] {
535 Value::Array(ref value) => value.clone(),
536 _ => vec![],
537 };
538 let has_children = !child_descriptions.is_empty();
539 let (children_body, children) = if has_children {
540 let body = child_descriptions
541 .iter()
542 .enumerate()
543 .map(|(i, child)| generate_node(&format!("{ident}{i}"), child))
544 .collect();
545 let idents = child_descriptions
546 .iter()
547 .enumerate()
548 .map(|(i, _)| Ident::new(&format!("{ident}{i}"), Span::call_site()))
549 .collect::<Vec<_>>();
550 (body, quote!(&[#(#idents),*]))
551 } else {
552 (quote!(), quote!())
553 };
554
555 let ident = Ident::new(ident, Span::call_site());
556
557 let style = quote!(taffy::style::Style {
558 #display
559 #direction
560 #position_type
561 #flex_direction
562 #flex_wrap
563 #overflow
564 #align_items
565 #align_self
566 #justify_items
567 #justify_self
568 #align_content
569 #justify_content
570 #flex_grow
571 #flex_shrink
572 #flex_basis
573 #gap
574 #grid_template_rows
575 #grid_template_columns
576 #grid_auto_rows
577 #grid_auto_columns
578 #grid_auto_flow
579 #grid_row
580 #grid_column
581 #size
582 #min_size
583 #max_size
584 #margin
585 #padding
586 #position
587 #border
588 ..Default::default()
589 });
590
591 if has_children {
592 quote!(
593 #children_body
594 let #ident = taffy.new_with_children(#style,#children).unwrap();
595 )
596 } else if measure_func.is_some() {
597 quote!(let #ident = taffy.new_leaf_with_measure(#style,#measure_func,).unwrap();)
598 } else {
599 quote!(let #ident = taffy.new_leaf(#style).unwrap();)
600 }
601}
602
603fn generate_size(size: &serde_json::Map<String, Value>) -> TokenStream {
604 dim_quoted!(size, width, generate_dimension, quote!(auto()));
605 dim_quoted!(size, height, generate_dimension, quote!(auto()));
606 quote!(
607 taffy::geometry::Size {
608 #width #height
609 }
610 )
611}
612
613fn generate_gap(size: &serde_json::Map<String, Value>) -> TokenStream {
614 dim_quoted_renamed!(size, column, width, generate_length_percentage, quote!(zero()));
615 dim_quoted_renamed!(size, row, height, generate_length_percentage, quote!(zero()));
616 quote!(
617 taffy::geometry::Size {
618 #width #height
619 }
620 )
621}
622
623fn generate_length_percentage(dimen: &serde_json::Map<String, Value>) -> TokenStream {
624 let unit = dimen.get("unit").unwrap();
625 let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
626
627 match unit {
628 Value::String(ref unit) => match unit.as_ref() {
629 "points" => {
630 let value = value();
631 quote!(taffy::style::LengthPercentage::Points(#value))
632 }
633 "percent" => {
634 let value = value();
635 quote!(taffy::style::LengthPercentage::Percent(#value))
636 }
637 _ => unreachable!(),
638 },
639 _ => unreachable!(),
640 }
641}
642
643fn generate_length_percentage_auto(dimen: &serde_json::Map<String, Value>) -> TokenStream {
644 let unit = dimen.get("unit").unwrap();
645 let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
646
647 match unit {
648 Value::String(ref unit) => match unit.as_ref() {
649 "auto" => quote!(taffy::style::LengthPercentageAuto::Auto),
650 "points" => {
651 let value = value();
652 quote!(taffy::style::LengthPercentageAuto::Points(#value))
653 }
654 "percent" => {
655 let value = value();
656 quote!(taffy::style::LengthPercentageAuto::Percent(#value))
657 }
658 _ => unreachable!(),
659 },
660 _ => unreachable!(),
661 }
662}
663
664fn generate_dimension(dimen: &serde_json::Map<String, Value>) -> TokenStream {
665 let unit = dimen.get("unit").unwrap();
666 let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
667
668 match unit {
669 Value::String(ref unit) => match unit.as_ref() {
670 "auto" => quote!(taffy::style::Dimension::Auto),
671 "points" => {
672 let value = value();
673 quote!(taffy::style::Dimension::Points(#value))
674 }
675 "percent" => {
676 let value = value();
677 quote!(taffy::style::Dimension::Percent(#value))
678 }
679 _ => unreachable!(),
680 },
681 _ => unreachable!(),
682 }
683}
684
685fn generate_grid_auto_flow(auto_flow: &serde_json::Map<String, Value>) -> TokenStream {
686 let direction = auto_flow.get("direction").unwrap().as_str().unwrap();
687 let algorithm = auto_flow.get("algorithm").unwrap().as_str().unwrap();
688
689 match (direction, algorithm) {
690 ("row", "sparse") => quote!(taffy::style::GridAutoFlow::Row),
691 ("column", "sparse") => quote!(taffy::style::GridAutoFlow::Column),
692 ("row", "dense") => quote!(taffy::style::GridAutoFlow::RowDense),
693 ("column", "dense") => quote!(taffy::style::GridAutoFlow::ColumnDense),
694 _ => unreachable!(),
695 }
696}
697
698fn generate_line(start: TokenStream, end: TokenStream) -> TokenStream {
699 quote!(taffy::geometry::Line { start:#start, end:#end })
700}
701
702fn generate_grid_position(grid_position: &serde_json::Map<String, Value>) -> TokenStream {
703 let kind = grid_position.get("kind").unwrap();
704 let value = || grid_position.get("value").unwrap().as_f64().unwrap() as f32;
705
706 match kind {
707 Value::String(ref kind) => match kind.as_ref() {
708 "auto" => quote!(taffy::style::GridPlacement::Auto),
709 "span" => {
710 let value = value() as u16;
711 quote!(taffy::style::GridPlacement::Span(#value))
712 }
713 "line" => {
714 let value = value() as i16;
715 quote!(taffy::style::GridPlacement::Line(#value))
716 }
717 _ => unreachable!(),
718 },
719 _ => unreachable!(),
720 }
721}
722
723fn generate_track_definition_list(raw_list: &[Value]) -> TokenStream {
724 let list = raw_list.iter().map(|obj| match obj {
725 Value::Object(inner) => generate_track_definition(inner),
726 _ => unreachable!(),
727 });
728
729 quote!(vec![#(#list),*])
730}
731
732fn generate_track_definition(track_definition: &serde_json::Map<String, Value>) -> TokenStream {
733 let kind = track_definition.get("kind").unwrap().as_str().unwrap();
734 let name = || track_definition.get("name").unwrap().as_str().unwrap();
735 let arguments = || track_definition.get("arguments").unwrap();
736
737 match kind {
738 "scalar" => generate_scalar_definition(track_definition),
739 "function" => match (name(), arguments()) {
740 ("fit-content", Value::Array(arguments)) => {
741 if arguments.len() != 1 {
742 panic!("fit-content function with the wrong number of arguments");
743 }
744 let argument = match arguments[0] {
745 Value::Object(ref arg) => generate_scalar_definition(arg),
746 _ => unreachable!(),
747 };
748 quote!(fit_content(#argument))
749 }
750 ("minmax", Value::Array(arguments)) => {
751 if arguments.len() != 2 {
752 panic!("minmax function with the wrong number of arguments");
753 }
754 let min = match arguments[0] {
755 Value::Object(ref arg) => generate_scalar_definition(arg),
756 _ => unreachable!(),
757 };
758 let max = match arguments[1] {
759 Value::Object(ref arg) => generate_scalar_definition(arg),
760 _ => unreachable!(),
761 };
762 quote!(minmax(#min, #max))
763 }
764 ("repeat", Value::Array(arguments)) => {
765 if arguments.len() < 2 {
766 panic!("repeat function with the wrong number of arguments");
767 }
768 let repetition = match arguments[0] {
769 Value::Object(ref arg) => {
770 let unit = arg.get("unit").unwrap().as_str().unwrap();
771 let value = || arg.get("value").unwrap().as_u64().unwrap() as u16;
772
773 match unit {
774 "auto-fill" => quote!(GridTrackRepetition::AutoFill),
775 // Not yet implemented in taffy
776 "auto-fit" => quote!(GridTrackRepetition::AutoFit),
777 // Not yet implemented in taffy
778 "integer" => {
779 let repetition_count = value();
780 quote!(GridTrackRepetition::Count(#repetition_count))
781 }
782 _ => unreachable!(),
783 }
784 }
785 _ => unreachable!(),
786 };
787 let track_list = generate_track_definition_list(&arguments[1..]);
788 quote!(repeat(#repetition, #track_list))
789 }
790 // TODO: Add support for fit-content
791 _ => unreachable!(),
792 },
793 _ => unreachable!(),
794 }
795}
796
797fn generate_scalar_definition(track_definition: &serde_json::Map<String, Value>) -> TokenStream {
798 let unit = || track_definition.get("unit").unwrap().as_str().unwrap();
799 let value = || track_definition.get("value").unwrap().as_f64().unwrap() as f32;
800
801 match unit() {
802 "auto" => quote!(auto()),
803 "min-content" => quote!(min_content()),
804 "max-content" => quote!(max_content()),
805 "points" | "percent" | "fraction" => {
806 let value = value();
807 match unit() {
808 "points" => quote!(points(#value)),
809 "percent" => quote!(percent(#value)),
810 "fraction" => quote!(flex(#value)),
811 _ => unreachable!(),
812 }
813 }
814 _ => unreachable!(),
815 }
816}
817
818fn generate_generic_measure_function() -> TokenStream {
819 quote!(
820 // WARNING: This function is generated by the gentest script. Do not edit directly
821 #[allow(dead_code)]
822 fn measure_standard_text(
823 known_dimensions: taffy::geometry::Size<Option<f32>>,
824 available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
825 text_content: &str,
826 ) -> taffy::geometry::Size<f32> {
827 use taffy::prelude::*;
828 const ZWS: char = '\u{200B}';
829 const H_WIDTH: f32 = 10.0;
830 const H_HEIGHT: f32 = 10.0;
831
832 if let Size { width: Some(width), height: Some(height) } = known_dimensions {
833 return Size { width, height };
834 }
835
836 let lines: Vec<&str> = text_content.split(ZWS).collect();
837 if lines.is_empty() {
838 return Size::ZERO;
839 }
840
841 let min_line_length: usize = lines.iter().map(|line| line.len()).max().unwrap_or(0);
842 let max_line_length: usize = lines.iter().map(|line| line.len()).sum();
843 let width = known_dimensions.width.unwrap_or_else(|| match available_space.width {
844 AvailableSpace::MinContent => min_line_length as f32 * H_WIDTH,
845 AvailableSpace::MaxContent => max_line_length as f32 * H_WIDTH,
846 AvailableSpace::Definite(width) => {
847 width.min(max_line_length as f32 * H_WIDTH).max(min_line_length as f32 * H_WIDTH)
848 }
849 });
850 let height = known_dimensions.height.unwrap_or_else(|| {
851 let width_line_length = (width / H_WIDTH).floor() as usize;
852 let mut line_count = 1;
853 let mut current_line_length = 0;
854 for line in &lines {
855 if current_line_length + line.len() > width_line_length {
856 if current_line_length > 0 {
857 line_count += 1
858 };
859 current_line_length = line.len();
860 } else {
861 current_line_length += line.len();
862 };
863 }
864 (line_count as f32) * H_HEIGHT
865 });
866
867 Size { width, height }
868 }
869 )
870}
871
872fn generate_measure_function(text_content: &str) -> TokenStream {
873 quote!(
874 taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
875 const TEXT : &str = #text_content;
876 super::measure_standard_text(known_dimensions, available_space, TEXT)
877 })
878 )
879}