1 //! `pest_derive` crate has large dependency tree, and, as a build dependency,
2 //! it imposes these deps onto our consumers.
3 //!
4 //! To avoid that, let's just dump generated code to string into this
5 //! repository, and add a test that checks that the code is fresh.
6 use std::{
7 fs,
8 io::Write,
9 process::{Command, Stdio},
10 time::Instant,
11 };
12
13 const PREAMBLE: &str = "\
14 //! This is @generated code, do not edit by hand.
15 //! See `semver.pest` and `genpest.rs`.
16 #![allow(unused_attributes)]
17 use super::SemverParser;
18 ";
19
20 #[test]
generated_code_is_fresh()21 fn generated_code_is_fresh() {
22 let t = Instant::now();
23
24 let token_stream = {
25 let grammar = include_str!("../src/semver.pest");
26 let input = format!(
27 r###"
28 #[derive(Parser)]
29 #[grammar_inline = r#"{}"#]
30 struct SemverParser;
31 "###,
32 grammar
33 )
34 .parse::<proc_macro2::TokenStream>()
35 .unwrap();
36
37 let ts = pest_generator::derive_parser(input.into(), true);
38 eprintln!("Generated code in {:02?}", t.elapsed());
39 ts
40 };
41
42 let current = {
43 let current = fs::read("./src/generated.rs").unwrap_or_default();
44 String::from_utf8(current).unwrap()
45 };
46
47 let is_up_to_date = {
48 let current = normalize(¤t[PREAMBLE.len()..]);
49 let generated = normalize(&token_stream.to_string());
50 current == generated
51 };
52
53 // Rustfmt takes ages on this input, so fast-path skip it for unchanged
54 // code.
55 if is_up_to_date {
56 return;
57 }
58
59 let code = {
60 eprintln!("Reformatting (this will take couple of minutes)");
61 let t = Instant::now();
62 let code = reformat(&token_stream.to_string());
63 let code = format!("{}\n{}", PREAMBLE, code);
64 eprintln!("Reformatted in {:02?}", t.elapsed());
65 code
66 };
67
68 fs::write("./src/generated.rs", code).unwrap();
69 panic!("Generated code in the repository is outdated, updating...");
70 }
71
reformat(code: &str) -> String72 fn reformat(code: &str) -> String {
73 let mut cmd = Command::new("rustfmt")
74 .args(&["--config", "tab_spaces=2"])
75 .stdin(Stdio::piped())
76 .stdout(Stdio::piped())
77 .spawn()
78 .unwrap();
79
80 cmd.stdin
81 .take()
82 .unwrap()
83 .write_all(code.as_bytes())
84 .unwrap();
85 let output = cmd.wait_with_output().unwrap();
86 assert!(output.status.success());
87 String::from_utf8(output.stdout).unwrap()
88 }
89
normalize(code: &str) -> String90 fn normalize(code: &str) -> String {
91 code.replace(|c: char| c.is_ascii_whitespace() || "{},".contains(c), "")
92 }
93