tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 496HTML 245JavaScript 8CSS 61214 Sutichaykuna

⎇ c83608da41161141b31ab5bf6961352e7870c505 scripts/gentest/src/main.rs Rust · 487 siq'ikuna · 16.3 KB Ñawpa willay Pi imata llamkarqan ch'uya

Add support for align-items: space-evenly (#218) c83608da4 · Nico Burns ·

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use fantoccini::{Client, ClientBuilder};
6use json;
7use log::*;
8use proc_macro2::{Span, TokenStream};
9use quote::quote;
10use syn::Ident;
11
12#[tokio::main]
13async fn main() {
14    env_logger::init();
15    // this requires being run by cargo, which is iffy
16    let root_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
17    let repo_root = root_dir.parent().and_then(Path::parent).unwrap();
18
19    let fixtures_root = repo_root.join("test_fixtures");
20    let fixtures = fs::read_dir(fixtures_root).unwrap();
21
22    info!("reading test fixtures from disk");
23    let mut fixtures: Vec<_> = fixtures
24        .into_iter()
25        .filter_map(|a| a.ok())
26        .filter(|f| f.path().is_file() && f.path().extension().map(|p| p == "html").unwrap_or(false))
27        .map(|f| {
28            let fixture_path = f.path().to_path_buf();
29            let name = fixture_path.file_stem().unwrap().to_str().unwrap().to_string();
30            (name, fixture_path)
31        })
32        .collect();
33    fixtures.sort_unstable_by_key(|f| f.0.clone());
34
35    info!("starting webdriver instance");
36    let webdriver_url = "http://localhost:4444";
37    let mut webdriver_handle = Command::new("chromedriver")
38        .arg("--port=4444")
39        .spawn()
40        .expect("ChromeDriver not found: Make sure you have it installed and added to your PATH.");
41
42    // this is silly, but it works
43    std::thread::sleep(std::time::Duration::from_secs(1));
44
45    let mut caps = serde_json::map::Map::new();
46    let chrome_opts = serde_json::json!({ "args": ["--headless", "--disable-gpu"] });
47    caps.insert("goog:chromeOptions".to_string(), chrome_opts.clone());
48
49    info!("spawning webdriver client and collecting test descriptions");
50    let client = ClientBuilder::native().capabilities(caps.clone()).connect(webdriver_url).await.unwrap();
51
52    let mut test_descs = vec![];
53    for (name, fixture_path) in fixtures {
54        test_descs.push(test_root_element(client.clone(), name, fixture_path).await);
55    }
56
57    info!("killing webdriver instance...");
58    webdriver_handle.kill().unwrap();
59
60    info!("generating test sources and concatenating...");
61
62    let bench_descs: Vec<_> = test_descs
63        .iter()
64        .map(|(name, description)| {
65            debug!("generating bench contents for {}", &name);
66            (name.clone(), generate_bench(description))
67        })
68        .collect();
69
70    let test_descs: Vec<_> = test_descs
71        .iter()
72        .map(|(name, description)| {
73            debug!("generating test contents for {}", &name);
74            (name.clone(), generate_test(name, description))
75        })
76        .collect();
77
78    let benchmarks: Vec<_> = test_descs
79        .iter()
80        .map(|(name, _)| {
81            let bench_mod = Ident::new(name, Span::call_site());
82            quote!(#bench_mod::compute())
83        })
84        .collect();
85
86    let test_mods = test_descs
87        .iter()
88        .map(|(name, _)| {
89            let name = Ident::new(name, Span::call_site());
90            quote!(mod #name;)
91        })
92        .fold(quote!(), |a, b| quote!(#a #b));
93
94    for (name, bench_body) in bench_descs {
95        let mut bench_filename = repo_root.join("benches").join("generated").join(&name);
96        bench_filename.set_extension("rs");
97        debug!("writing {} to disk...", &name);
98        fs::write(bench_filename, bench_body.to_string()).unwrap();
99    }
100
101    for (name, test_body) in test_descs {
102        let mut test_filename = repo_root.join("tests").join("generated").join(&name);
103        test_filename.set_extension("rs");
104        debug!("writing {} to disk...", &name);
105        fs::write(test_filename, test_body.to_string()).unwrap();
106    }
107
108    let bench_mods = quote!(
109        use criterion::{criterion_group, criterion_main, Criterion};
110
111        #test_mods
112
113        fn benchmark(c: &mut Criterion) {
114            c.bench_function("generated benchmarks", |b| {
115                b.iter(|| { #(#benchmarks;)* })
116            });
117        }
118
119        criterion_group!(benches, benchmark);
120        criterion_main!(benches);
121    );
122
123    info!("writing generated test file to disk...");
124    fs::write(repo_root.join("benches").join("generated").join("mod.rs"), bench_mods.to_string()).unwrap();
125    fs::write(repo_root.join("tests").join("generated").join("mod.rs"), test_mods.to_string()).unwrap();
126
127    info!("formatting the source directory");
128    Command::new("cargo").arg("fmt").current_dir(repo_root).status().unwrap();
129}
130
131async fn test_root_element(client: Client, name: String, fixture_path: impl AsRef<Path>) -> (String, json::JsonValue) {
132    let fixture_path = fixture_path.as_ref();
133
134    let url = format!("file://{}", fixture_path.display());
135
136    client.goto(&url).await.unwrap();
137    let description = client
138        .execute("return JSON.stringify(describeElement(document.getElementById('test-root')))", vec![])
139        .await
140        .unwrap();
141    let description_string = description.as_str().unwrap();
142    let description = json::parse(description_string).unwrap();
143    (name, description)
144}
145
146fn generate_bench(description: &json::JsonValue) -> TokenStream {
147    let node_description = generate_node("node", &description);
148
149    quote!(
150        pub fn compute() {
151            let mut taffy = taffy::Taffy::new();
152            #node_description
153            taffy.compute_layout(node, taffy::geometry::Size::NONE).unwrap();
154        }
155    )
156}
157
158fn generate_test(name: impl AsRef<str>, description: &json::JsonValue) -> TokenStream {
159    let name = name.as_ref();
160    let name = Ident::new(name, Span::call_site());
161    let node_description = generate_node("node", &description);
162    let assertions = generate_assertions("node", &description);
163
164    quote!(
165        #[test]
166        fn #name() {
167            let mut taffy = taffy::Taffy::new();
168            #node_description
169            taffy.compute_layout(node, taffy::geometry::Size::NONE).unwrap();
170            #assertions
171        }
172    )
173}
174
175fn generate_assertions(ident: &str, node: &json::JsonValue) -> TokenStream {
176    let layout = &node["layout"];
177
178    let read_f32 = |s: &str| layout[s].as_f32().unwrap();
179    let width = read_f32("width");
180    let height = read_f32("height");
181    let x = read_f32("x");
182    let y = read_f32("y");
183
184    let children = {
185        let mut c = Vec::new();
186        match node["children"] {
187            json::JsonValue::Array(ref value) => {
188                for i in 0..value.len() {
189                    let child = &value[i];
190                    c.push(generate_assertions(&format!("{}{}", ident, i), child));
191                }
192            }
193            _ => (),
194        };
195        c.into_iter().fold(quote!(), |a, b| quote!(#a #b))
196    };
197
198    let ident = Ident::new(ident, Span::call_site());
199
200    quote!(
201        assert_eq!(taffy.layout(#ident).unwrap().size.width, #width);
202        assert_eq!(taffy.layout(#ident).unwrap().size.height, #height);
203        assert_eq!(taffy.layout(#ident).unwrap().location.x, #x);
204        assert_eq!(taffy.layout(#ident).unwrap().location.y, #y);
205
206        #children
207    )
208}
209
210fn generate_node(ident: &str, node: &json::JsonValue) -> TokenStream {
211    let style = &node["style"];
212
213    let display = match style["display"] {
214        json::JsonValue::Short(ref value) => match value.as_ref() {
215            "none" => quote!(display: taffy::style::Display::None,),
216            _ => quote!(),
217        },
218        _ => quote!(),
219    };
220
221    let position_type = match style["position_type"] {
222        json::JsonValue::Short(ref value) => match value.as_ref() {
223            "absolute" => quote!(position_type: taffy::style::PositionType::Absolute,),
224            _ => quote!(),
225        },
226        _ => quote!(),
227    };
228
229    let direction = match style["direction"] {
230        json::JsonValue::Short(ref value) => match value.as_ref() {
231            "rtl" => quote!(direction: taffy::style::Direction::RTL,),
232            "ltr" => quote!(direction: taffy::style::Direction::LTR,),
233            _ => quote!(),
234        },
235        _ => quote!(),
236    };
237
238    let flex_direction = match style["flexDirection"] {
239        json::JsonValue::Short(ref value) => match value.as_ref() {
240            "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,),
241            "column" => quote!(flex_direction: taffy::style::FlexDirection::Column,),
242            "column-reverse" => quote!(flex_direction: taffy::style::FlexDirection::ColumnReverse,),
243            _ => quote!(),
244        },
245        _ => quote!(),
246    };
247
248    let flex_wrap = match style["flexWrap"] {
249        json::JsonValue::Short(ref value) => match value.as_ref() {
250            "wrap" => quote!(flex_wrap: taffy::style::FlexWrap::Wrap,),
251            "wrap-reverse" => quote!(flex_wrap: taffy::style::FlexWrap::WrapReverse,),
252            _ => quote!(),
253        },
254        _ => quote!(),
255    };
256
257    let overflow = match style["overflow"] {
258        json::JsonValue::Short(ref value) => match value.as_ref() {
259            "hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
260            "scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
261            _ => quote!(),
262        },
263        _ => quote!(),
264    };
265
266    let align_items = match style["alignItems"] {
267        json::JsonValue::Short(ref value) => match value.as_ref() {
268            "flex-start" => quote!(align_items: taffy::style::AlignItems::FlexStart,),
269            "flex-end" => quote!(align_items: taffy::style::AlignItems::FlexEnd,),
270            "center" => quote!(align_items: taffy::style::AlignItems::Center,),
271            "baseline" => quote!(align_items: taffy::style::AlignItems::Baseline,),
272            _ => quote!(),
273        },
274        _ => quote!(),
275    };
276
277    let align_self = match style["alignSelf"] {
278        json::JsonValue::Short(ref value) => match value.as_ref() {
279            "flex-start" => quote!(align_self: taffy::style::AlignSelf::FlexStart,),
280            "flex-end" => quote!(align_self: taffy::style::AlignSelf::FlexEnd,),
281            "center" => quote!(align_self: taffy::style::AlignSelf::Center,),
282            "baseline" => quote!(align_self: taffy::style::AlignSelf::Baseline,),
283            "stretch" => quote!(align_self: taffy::style::AlignSelf::Stretch,),
284            _ => quote!(),
285        },
286        _ => quote!(),
287    };
288
289    let align_content = match style["alignContent"] {
290        json::JsonValue::Short(ref value) => match value.as_ref() {
291            "flex-start" => quote!(align_content: taffy::style::AlignContent::FlexStart,),
292            "flex-end" => quote!(align_content: taffy::style::AlignContent::FlexEnd,),
293            "center" => quote!(align_content: taffy::style::AlignContent::Center,),
294            "space-between" => quote!(align_content: taffy::style::AlignContent::SpaceBetween,),
295            "space-around" => quote!(align_content: taffy::style::AlignContent::SpaceAround,),
296            "space-evenly" => quote!(align_content: taffy::style::AlignContent::SpaceEvenly,),
297            _ => quote!(),
298        },
299        _ => quote!(),
300    };
301
302    let justify_content = match style["justifyContent"] {
303        json::JsonValue::Short(ref value) => match value.as_ref() {
304            "flex-end" => quote!(justify_content: taffy::style::JustifyContent::FlexEnd,),
305            "center" => quote!(justify_content: taffy::style::JustifyContent::Center,),
306            "space-between" => quote!(justify_content: taffy::style::JustifyContent::SpaceBetween,),
307            "space-around" => quote!(justify_content: taffy::style::JustifyContent::SpaceAround,),
308            "space-evenly" => quote!(justify_content: taffy::style::JustifyContent::SpaceEvenly,),
309            _ => quote!(),
310        },
311        _ => quote!(),
312    };
313
314    let flex_grow = match style["flexGrow"] {
315        json::JsonValue::Number(value) => {
316            let value: f32 = value.into();
317            quote!(flex_grow: #value,)
318        }
319        _ => quote!(),
320    };
321
322    let flex_shrink = match style["flexShrink"] {
323        json::JsonValue::Number(value) => {
324            let value: f32 = value.into();
325            quote!(flex_shrink: #value,)
326        }
327        _ => quote!(),
328    };
329
330    let flex_basis = match style["flexBasis"] {
331        json::JsonValue::Object(ref value) => {
332            let value = generate_dimension(value);
333            quote!(flex_basis: #value,)
334        }
335        _ => quote!(),
336    };
337
338    let size = match style["size"] {
339        json::JsonValue::Object(ref value) => {
340            let size = generate_size(value);
341            quote!(size: #size,)
342        }
343        _ => quote!(),
344    };
345
346    let min_size = match style["min_size"] {
347        json::JsonValue::Object(ref value) => {
348            let min_size = generate_size(value);
349            quote!(min_size: #min_size,)
350        }
351        _ => quote!(),
352    };
353
354    let max_size = match style["max_size"] {
355        json::JsonValue::Object(ref value) => {
356            let max_size = generate_size(value);
357            quote!(max_size: #max_size,)
358        }
359        _ => quote!(),
360    };
361
362    macro_rules! edges_quoted {
363        ($style:ident, $val:ident) => {
364            let $val = match $style[stringify!($val)] {
365                json::JsonValue::Object(ref value) => {
366                    let edges = generate_edges(value);
367                    quote!($val: #edges,)
368                },
369                _ => quote!(),
370            };
371        };
372    }
373
374    edges_quoted!(style, margin);
375    edges_quoted!(style, padding);
376    edges_quoted!(style, position);
377    edges_quoted!(style, border);
378
379    let (children_body, children) = match node["children"] {
380        json::JsonValue::Array(ref value) => {
381            if value.len() > 0 {
382                let body = value
383                    .iter()
384                    .enumerate()
385                    .map(|(i, child)| generate_node(&format!("{}{}", ident, i), child))
386                    .collect();
387                let idents = value
388                    .iter()
389                    .enumerate()
390                    .map(|(i, _)| Ident::new(&format!("{}{}", ident, i), Span::call_site()))
391                    .collect::<Vec<_>>();
392                (body, quote!(&[#(#idents),*]))
393            } else {
394                (quote!(), quote!(&[]))
395            }
396        }
397        _ => (quote!(), quote!()),
398    };
399
400    let ident = Ident::new(&format!("{}", ident), Span::call_site());
401
402    quote!(
403        #children_body
404        let #ident = taffy.new_with_children(
405        taffy::style::FlexboxLayout {
406            #display
407            #direction
408            #position_type
409            #flex_direction
410            #flex_wrap
411            #overflow
412            #align_items
413            #align_self
414            #align_content
415            #justify_content
416            #flex_grow
417            #flex_shrink
418            #flex_basis
419            #size
420            #min_size
421            #max_size
422            #margin
423            #padding
424            #position
425            #border
426            ..Default::default()
427        },
428        #children
429        // TODO: Only add children if they exist
430    ).unwrap();)
431}
432
433macro_rules! dim_quoted {
434    ($obj:ident, $dim_name:ident) => {
435        let $dim_name = match $obj.get(stringify!($dim_name)) {
436            Some(json::JsonValue::Object(ref value)) => {
437                let dim = generate_dimension(value);
438                quote!($dim_name: #dim,)
439            }
440            _ => quote!(),
441        };
442    };
443}
444
445fn generate_size(size: &json::object::Object) -> TokenStream {
446    dim_quoted!(size, width);
447    dim_quoted!(size, height);
448    quote!(
449        taffy::geometry::Size {
450            #width #height
451            ..Default::default()
452        }
453    )
454}
455
456fn generate_dimension(dimen: &json::object::Object) -> TokenStream {
457    let unit = dimen.get("unit").unwrap();
458    let value = || dimen.get("value").unwrap().as_f32().unwrap();
459
460    match unit {
461        json::JsonValue::Short(ref unit) => match unit.as_ref() {
462            "auto" => quote!(taffy::style::Dimension::Auto),
463            "points" => {
464                let value = value();
465                quote!(taffy::style::Dimension::Points(#value))
466            }
467            "percent" => {
468                let value = value();
469                quote!(taffy::style::Dimension::Percent(#value))
470            }
471            _ => unreachable!(),
472        },
473        _ => unreachable!(),
474    }
475}
476
477fn generate_edges(dimen: &json::object::Object) -> TokenStream {
478    dim_quoted!(dimen, start);
479    dim_quoted!(dimen, end);
480    dim_quoted!(dimen, top);
481    dim_quoted!(dimen, bottom);
482
483    quote!(taffy::geometry::Rect {
484        #start #end #top #bottom
485        ..Default::default()
486    })
487}