rusteron_code_gen/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(clippy::all)]
5#![allow(unused_unsafe)]
6#![allow(unused_variables)]
7#![doc = include_str!("../README.md")]
8
9mod common;
10mod generator;
11mod parser;
12
13pub use common::*;
14pub use generator::*;
15pub use parser::*;
16
17use proc_macro2::TokenStream;
18use std::fs::OpenOptions;
19use std::io::Write;
20use std::process::{Command, Stdio};
21
22pub const CUSTOM_AERON_CODE: &str = include_str!("./aeron_custom.rs");
23pub const CUSTOM_RB_CODE: &str = include_str!("./rb_custom.rs");
24pub const COMMON_CODE: &str = include_str!("./common.rs");
25
26pub fn append_to_file(file_path: &str, code: &str) -> std::io::Result<()> {
27    // Open the file in append mode
28    let mut file = OpenOptions::new()
29        .create(true)
30        .write(true)
31        .append(true)
32        .open(file_path)?;
33
34    // Write the generated code to the file
35    writeln!(file, "\n{}", code)?;
36
37    Ok(())
38}
39
40#[allow(dead_code)]
41pub fn format_with_rustfmt(code: &str) -> Result<String, std::io::Error> {
42    let mut rustfmt = Command::new("rustfmt")
43        .stdin(Stdio::piped())
44        .stdout(Stdio::piped())
45        .spawn()?;
46
47    if let Some(mut stdin) = rustfmt.stdin.take() {
48        stdin.write_all(code.as_bytes())?;
49    }
50
51    let output = rustfmt.wait_with_output()?;
52    let formatted_code = String::from_utf8_lossy(&output.stdout).to_string();
53
54    Ok(formatted_code)
55}
56
57#[allow(dead_code)]
58pub fn format_token_stream(tokens: TokenStream) -> String {
59    let code = tokens.to_string();
60
61    match format_with_rustfmt(&code) {
62        Ok(formatted_code) if !formatted_code.trim().is_empty() => formatted_code,
63        _ => code.replace("{", "{\n"), // Fallback to unformatted code in case of error
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use crate::generator::MEDIA_DRIVER_BINDINGS;
70    use crate::parser::parse_bindings;
71    use crate::{
72        append_to_file, format_token_stream, format_with_rustfmt, ARCHIVE_BINDINGS,
73        CLIENT_BINDINGS, CUSTOM_AERON_CODE, RB,
74    };
75    use proc_macro2::TokenStream;
76    use std::fs;
77
78    #[test]
79    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
80    fn media_driver() {
81        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/media-driver.rs".into());
82        assert_eq!(
83            "AeronImageFragmentAssembler",
84            bindings
85                .wrappers
86                .get("aeron_image_fragment_assembler_t")
87                .unwrap()
88                .class_name
89        );
90
91        let file = write_to_file(TokenStream::new(), true, "md.rs");
92
93        let bindings_copy = bindings.clone();
94        for handler in bindings.handlers.iter_mut() {
95            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
96            let _ = crate::generate_handlers(handler, &bindings_copy);
97        }
98        for (p, w) in bindings
99            .wrappers
100            .values()
101            .filter(|w| !w.type_name.contains("_t_") && w.type_name != "in_addr")
102            .enumerate()
103        {
104            let code = crate::generate_rust_code(
105                w,
106                &bindings.wrappers,
107                p == 0,
108                true,
109                true,
110                &bindings.handlers,
111            );
112            write_to_file(code, false, "md.rs");
113        }
114        let bindings_copy = bindings.clone();
115        for handler in bindings.handlers.iter_mut() {
116            let code = crate::generate_handlers(handler, &bindings_copy);
117            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
118        }
119        let t = trybuild::TestCases::new();
120        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
121        append_to_file(&file, MEDIA_DRIVER_BINDINGS).unwrap();
122        append_to_file(&file, "}").unwrap();
123        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
124        append_to_file(&file, "\npub fn main() {}\n").unwrap();
125        t.pass(&file)
126    }
127
128    #[test]
129    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
130    fn client() {
131        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/client.rs".into());
132        assert_eq!(
133            "AeronImageFragmentAssembler",
134            bindings
135                .wrappers
136                .get("aeron_image_fragment_assembler_t")
137                .unwrap()
138                .class_name
139        );
140        assert_eq!(
141            0,
142            bindings.methods.len(),
143            "expected all methods to have been matched {:#?}",
144            bindings.methods
145        );
146
147        let file = write_to_file(TokenStream::new(), true, "client.rs");
148        let bindings_copy = bindings.clone();
149        for handler in bindings.handlers.iter_mut() {
150            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
151            let _ = crate::generate_handlers(handler, &bindings_copy);
152        }
153        for (p, w) in bindings.wrappers.values().enumerate() {
154            let code = crate::generate_rust_code(
155                w,
156                &bindings.wrappers,
157                p == 0,
158                true,
159                true,
160                &bindings.handlers,
161            );
162            write_to_file(code, false, "client.rs");
163        }
164        let bindings_copy = bindings.clone();
165        for handler in bindings.handlers.iter_mut() {
166            let code = crate::generate_handlers(handler, &bindings_copy);
167            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
168        }
169
170        let t = trybuild::TestCases::new();
171        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
172        append_to_file(&file, CLIENT_BINDINGS).unwrap();
173        append_to_file(&file, "}").unwrap();
174        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
175        append_to_file(&file, "\npub fn main() {}\n").unwrap();
176        t.pass(file)
177    }
178
179    #[test]
180    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
181    fn rb() {
182        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/rb.rs".into());
183
184        let file = write_to_file(TokenStream::new(), true, "rb.rs");
185
186        let bindings_copy = bindings.clone();
187        for handler in bindings.handlers.iter_mut() {
188            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
189            let _ = crate::generate_handlers(handler, &bindings_copy);
190        }
191
192        for (p, w) in bindings.wrappers.values().enumerate() {
193            let code = crate::generate_rust_code(
194                w,
195                &bindings.wrappers,
196                p == 0,
197                true,
198                false,
199                &bindings.handlers,
200            );
201            if code.to_string().contains("ndler : Option < AeronCloseClientHandlerImpl > , rbd :) -> Result < Self , AeronCError > { let resource = Manage") {
202                panic!("{}", format_token_stream(code));
203            }
204
205            write_to_file(code, false, "rb.rs");
206        }
207
208        let bindings_copy = bindings.clone();
209        for handler in bindings.handlers.iter_mut() {
210            let code = crate::generate_handlers(handler, &bindings_copy);
211            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
212        }
213
214        let t = trybuild::TestCases::new();
215        append_to_file(&file, RB).unwrap();
216        append_to_file(&file, "\npub fn main() {}\n").unwrap();
217        t.pass(file)
218    }
219
220    #[test]
221    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
222    fn archive() {
223        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/archive.rs".into());
224        assert_eq!(
225            "AeronImageFragmentAssembler",
226            bindings
227                .wrappers
228                .get("aeron_image_fragment_assembler_t")
229                .unwrap()
230                .class_name
231        );
232
233        let file = write_to_file(TokenStream::new(), true, "archive.rs");
234        let bindings_copy = bindings.clone();
235        for handler in bindings.handlers.iter_mut() {
236            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
237            let _ = crate::generate_handlers(handler, &bindings_copy);
238        }
239        for (p, w) in bindings.wrappers.values().enumerate() {
240            let code = crate::generate_rust_code(
241                w,
242                &bindings.wrappers,
243                p == 0,
244                true,
245                true,
246                &bindings.handlers,
247            );
248            write_to_file(code, false, "archive.rs");
249        }
250        let bindings_copy = bindings.clone();
251        for handler in bindings.handlers.iter_mut() {
252            let code = crate::generate_handlers(handler, &bindings_copy);
253            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
254        }
255        let t = trybuild::TestCases::new();
256        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
257        append_to_file(&file, ARCHIVE_BINDINGS).unwrap();
258        append_to_file(&file, "}").unwrap();
259        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
260        append_to_file(&file, "\npub fn main() {}\n").unwrap();
261        t.pass(file)
262    }
263
264    fn write_to_file(rust_code: TokenStream, delete: bool, name: &str) -> String {
265        let src = format_token_stream(rust_code);
266        let path = format!("../target/{name}");
267        let path = &path;
268        if delete {
269            let _ = fs::remove_file(path);
270        }
271        append_to_file(path, &src).unwrap();
272        path.to_string()
273    }
274}