1#![deny(missing_docs)]
8
9mod generator;
12mod naming;
13mod parser;
14mod syntax;
15mod writer;
16
17pub use generator::{
18 cpp::{fragment::CppFragment, GeneratedCppBlocks},
19 rust::GeneratedRustBlocks,
20};
21pub use parser::Parser;
22pub use syntax::{parse_qt_file, CxxQtFile, CxxQtItem};
23pub use writer::{cpp::write_cpp, rust::write_rust};
24
25pub use syn::{Error, Result};
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30
31 use crate::generator::cpp::property::tests::require_pair;
32 use clang_format::{clang_format_with_style, ClangFormatStyle};
33 use generator::{cpp::GeneratedCppBlocks, rust::GeneratedRustBlocks};
34 use parser::Parser;
35 use pretty_assertions::assert_str_eq;
36 use proc_macro2::TokenStream;
37 use quote::{quote, ToTokens};
38 use std::{
39 env,
40 fs::OpenOptions,
41 io::Write,
42 path::{Path, PathBuf},
43 };
44 use writer::{cpp::write_cpp, rust::write_rust};
45
46 pub fn assert_tokens_eq<T: ToTokens>(item: &T, tokens: TokenStream) {
48 let left = tokens.to_string();
55 let right = item.to_token_stream().to_string();
56 if left != right {
57 assert_str_eq!(format_rs_source(&left), format_rs_source(&right));
58 assert_str_eq!(left, right);
62 }
64 }
65
66 macro_rules! assert_parse_errors {
67 { $parse_fn:expr => $($input:tt)* } => {
68 $(assert!($parse_fn(syn::parse_quote! $input).is_err());)*
69 }
70 }
71 pub(crate) use assert_parse_errors;
72
73 pub(crate) fn format_cpp(cpp_code: &str) -> String {
75 clang_format_with_style(cpp_code, &ClangFormatStyle::File).unwrap()
76 }
77
78 fn format_rs_source(rs_code: &str) -> String {
80 let mut command = std::process::Command::new("rustfmt");
82 let mut child = command
83 .args(["--emit", "stdout"])
84 .stdin(std::process::Stdio::piped())
85 .stdout(std::process::Stdio::piped())
86 .spawn()
87 .unwrap();
88
89 {
91 let mut stdin = child.stdin.take().unwrap();
92 write!(stdin, "{rs_code}").unwrap();
93 }
94
95 let output = child.wait_with_output().unwrap();
96 let output = String::from_utf8(output.stdout).unwrap();
97
98 output.replace("\n\n", "\n")
101 }
102
103 fn sanitize_code(mut code: String) -> String {
104 code.retain(|c| c != '\r');
105 code
106 }
107
108 fn update_expected_file(path: PathBuf, source: &str) {
110 println!("Updating expected file: {path:?}");
111
112 let mut file = OpenOptions::new()
113 .write(true)
114 .truncate(true)
115 .open(path)
116 .unwrap();
117 file.write_all(source.as_bytes()).unwrap();
118 }
119 fn update_expected(test_name: &str, rust: &str, header: &str, source: &str) -> bool {
122 if let Ok(path) = env::var("CXX_QT_UPDATE_EXPECTED") {
131 let output_folder = Path::new(&path);
133 let output_folder = output_folder.join("test_outputs");
134
135 let update = |file_ending, contents| {
136 update_expected_file(
137 output_folder.join(format!("{test_name}.{file_ending}")),
138 contents,
139 );
140 };
141 update("rs", rust);
142 update("h", header);
143 update("cpp", source);
144
145 true
146 } else {
148 false
149 }
150 }
151
152 fn test_code_generation_internal(
153 test_name: &str,
154 input: &str,
155 expected_rust_output: &str,
156 expected_cpp_header: &str,
157 expected_cpp_source: &str,
158 ) {
159 let parser = Parser::from(syn::parse_str(input).unwrap()).unwrap();
160
161 let generated_cpp = GeneratedCppBlocks::from(&parser).unwrap();
162 let (mut header, mut source) =
163 require_pair(&write_cpp(&generated_cpp, "directory/file_ident")).unwrap();
164 header = sanitize_code(header);
165 source = sanitize_code(source);
166
167 let generated_rust = GeneratedRustBlocks::from(&parser).unwrap();
168 let rust = sanitize_code(format_rs_source(
169 &write_rust(&generated_rust, Some("directory/file_ident")).to_string(),
170 ));
171
172 if !update_expected(test_name, &rust, &header, &source) {
174 assert_str_eq!(sanitize_code(expected_cpp_header.to_owned()), header);
175 assert_str_eq!(sanitize_code(expected_cpp_source.to_owned()), source);
176 assert_str_eq!(sanitize_code(expected_rust_output.to_owned()), rust);
177 }
178 }
180
181 macro_rules! test_code_generation {
184 ( $file_stem:literal ) => {
185 test_code_generation_internal(
186 $file_stem,
187 include_str!(concat!("../test_inputs/", $file_stem, ".rs")),
188 include_str!(concat!("../test_outputs/", $file_stem, ".rs")),
189 include_str!(concat!("../test_outputs/", $file_stem, ".h")),
190 include_str!(concat!("../test_outputs/", $file_stem, ".cpp")),
191 );
192 };
193 }
194
195 #[test]
196 fn generates_invokables() {
197 test_code_generation!("invokables");
198 }
199
200 #[test]
201 fn generates_passthrough_and_naming() {
202 test_code_generation!("passthrough_and_naming");
203 }
204
205 #[test]
206 fn generates_properties() {
207 test_code_generation!("properties");
208 }
209
210 #[test]
211 fn generates_signals() {
212 test_code_generation!("signals");
213 }
214
215 #[test]
216 fn generates_inheritance() {
217 test_code_generation!("inheritance");
218 }
219
220 #[test]
221 fn generates_qenum() {
222 test_code_generation!("qenum");
223 }
224
225 #[test]
226 #[should_panic]
227 fn fail_token_assert() {
228 assert_tokens_eq(
229 "e! { struct MyStruct; },
230 quote! { struct MyOtherStruct; },
231 )
232 }
233}