1use regex::Regex;
12use std::{
13 env,
14 fmt::{Debug, Display},
15 fs::{self, File},
16 io::{BufRead, BufReader},
17 path::{Path, PathBuf},
18};
19
20mod model;
21use model::{Model, Simulink};
22
23#[derive(Debug, Default, Clone)]
31pub struct Sys {
32 controller: Option<String>,
33 sources: Vec<PathBuf>,
34 headers: Vec<PathBuf>,
35}
36
37pub struct Builder {
38 controller_type: Option<String>,
39 sys_folder: String,
40}
41impl Default for Builder {
42 fn default() -> Self {
43 Self {
44 controller_type: Default::default(),
45 sys_folder: "sys".into(),
46 }
47 }
48}
49impl Builder {
50 pub fn name<S: Into<String>>(mut self, rs_type: S) -> Self {
54 self.controller_type = Some(rs_type.into());
55 self
56 }
57 pub fn folder<S: Into<String>>(mut self, folder: S) -> Self {
61 self.sys_folder = folder.into();
62 self
63 }
64 pub fn build(self) -> Sys {
66 let sys = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(self.sys_folder);
67
68 let mut sources = vec![];
69 let mut headers = vec![];
70
71 if let Ok(entries) = fs::read_dir(&sys) {
72 for entry in entries {
73 if let Ok(entry) = entry {
74 let file_name = entry.path();
75 if let Some(extension) = file_name.extension() {
76 match extension.to_str() {
77 Some("c") => {
78 sources.push(file_name);
79 }
80 Some("h") => {
81 headers.push(file_name);
82 }
83 _ => (),
84 }
85 }
86 }
87 }
88 }
89
90 Sys {
91 controller: self.controller_type,
92 sources,
93 headers,
94 }
95 }
96}
97
98impl Sys {
99 pub fn new<S: Into<String>>(rs_type: Option<S>) -> Self {
103 let sys = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("sys");
104
105 let mut sources = vec![];
106 let mut headers = vec![];
107
108 if let Ok(entries) = fs::read_dir(&sys) {
109 for entry in entries {
110 if let Ok(entry) = entry {
111 let file_name = entry.path();
112 if let Some(extension) = file_name.extension() {
113 match extension.to_str() {
114 Some("c") => {
115 sources.push(file_name);
116 }
117 Some("h") => {
118 headers.push(file_name);
119 }
120 _ => (),
121 }
122 }
123 }
124 }
125 }
126
127 Self {
128 controller: rs_type.map(|x| x.into()),
129 sources,
130 headers,
131 }
132 }
133 pub fn builder() -> Builder {
135 Default::default()
136 }
137 fn header(&self) -> Option<&str> {
139 self.headers.iter().find_map(|header| {
140 header.to_str().filter(|f| {
141 !(f.ends_with("rtwtypes.h")
142 || f.ends_with("rt_defines.h")
143 || f.ends_with("_private.h")
144 || f.ends_with("_types.h"))
145 })
146 })
147 }
148 fn parse_header(&self) -> Model {
153 let Some(header) = self.header() else {
154 panic!("cannot find error in sys")
155 };
156 let file = File::open(header).expect(&format!("file {:?} not found", header));
157 let reader = BufReader::new(file);
158 let mut lines = reader.lines();
159
160 let mut model = Model::default();
161 model.name = loop {
162 if let Some(Ok(line)) = lines.next() {
163 if line.contains("File:") {
164 let regex = Regex::new(r"File:\s*(\w+)\.h").unwrap();
165 if let Some(captures) = regex.captures(&line) {
166 let name = captures.get(1).unwrap().as_str();
167 break name.to_string();
168 }
169 }
170 }
171 };
172 while let Some(data) = Simulink::parse_io(&mut lines) {
173 model.simulink.push(data);
174 }
175 model
176 }
177 pub fn compile(&self) -> &Self {
179 let mut cc_builder = cc::Build::new();
180 self.sources
181 .iter()
182 .fold(&mut cc_builder, |cc_builder, source| {
183 cc_builder.file(source)
184 });
185 let bindings_builder = self
186 .headers
187 .iter()
188 .fold(bindgen::builder(), |bindings, header| {
189 println!("cargo:rerun-if-changed={:}", header.to_str().unwrap());
190 bindings.header(
191 header
192 .to_str()
193 .expect(&format!("{:?} conversion to str failed", header)),
194 )
195 });
196
197 let lib = env::var("CARGO_PKG_NAME").unwrap();
198 println!("cargo:rustc-link-search=native=lib{}", lib);
199 println!("cargo:rustc-link-lib={}", lib);
200
201 cc_builder.compile(lib.as_str());
202 let bindings = bindings_builder
203 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
204 .generate()
205 .expect("Unable to generate bindings");
206 let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
207 bindings
208 .write_to_file(out_path.join("bindings.rs"))
209 .expect("Couldn't write bindings!");
210 self
211 }
212 pub fn generate_module(&self) {
214 let out_dir = env::var_os("OUT_DIR").unwrap();
215 let dest_path = Path::new(&out_dir).join("controller.rs");
216 fs::write(&dest_path, format!("{}", self)).unwrap();
217 }
218}
219
220impl Display for Sys {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 let model = self.parse_header();
223 if let Some(controller) = self.controller.as_ref() {
224 writeln!(f, "/// Rust binder to Simulink C controller wrapper")?;
225 writeln!(f, "#[allow(dead_code)]")?;
226 writeln!(f, "pub type {} = {};", controller, model.name)?;
227 }
228 <Model as Display>::fmt(&model, f)
229 }
230}