1use std::collections::{BTreeMap, BTreeSet};
4use std::path::PathBuf;
5
6use serde::Deserialize;
7
8use super::{emit, emit_files, generate_bindings};
9
10#[derive(Debug)]
11pub struct GeneratedBindings {
12 pub command_ids: BTreeMap<String, String>,
14 pub rust: String,
15 pub javascript: String,
16 pub declarations: String,
17 pub rust_files: BTreeMap<String, String>,
19 pub javascript_files: BTreeMap<String, String>,
21 pub declaration_files: BTreeMap<String, String>,
23 pub cpp_files: BTreeMap<String, String>,
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum OutputMode {
30 Write,
31 Verify,
32}
33
34#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
36#[serde(rename_all = "snake_case")]
37pub enum Facade {
38 #[default]
40 Application,
41 Framework,
43}
44
45#[derive(Debug)]
46pub(super) struct Output {
47 pub(super) path: PathBuf,
48 pub(super) mode: OutputMode,
49}
50
51pub(super) struct BuildGeneration {
52 pub(super) bindings: GeneratedBindings,
53 pub(super) rerun_paths: BTreeSet<PathBuf>,
54}
55
56#[derive(Debug)]
58pub struct Generator {
59 manifest: PathBuf,
60 rust: Option<Output>,
61 rust_file: Option<Output>,
62 javascript: Option<Output>,
63 cpp: Option<Output>,
64 cpp_include_root: String,
65 facade: Option<Facade>,
66}
67
68impl Generator {
69 #[must_use]
72 pub fn new() -> Self {
73 Self {
74 manifest: std::env::var_os("CARGO_MANIFEST_DIR")
75 .map_or_else(|| PathBuf::from("."), PathBuf::from)
76 .join("tuffite.json"),
77 rust: None,
78 rust_file: None,
79 javascript: None,
80 cpp: None,
81 cpp_include_root: "tuffite/framework/shell_api".to_owned(),
82 facade: None,
83 }
84 }
85
86 #[must_use]
88 pub fn manifest(mut self, path: impl Into<PathBuf>) -> Self {
89 self.manifest = path.into();
90 self
91 }
92
93 #[must_use]
95 pub fn rust(mut self, directory: impl Into<PathBuf>, mode: OutputMode) -> Self {
96 self.rust = Some(Output::new(directory, mode));
97 self
98 }
99
100 #[must_use]
102 pub fn javascript(mut self, directory: impl Into<PathBuf>, mode: OutputMode) -> Self {
103 self.javascript = Some(Output::new(directory, mode));
104 self
105 }
106
107 #[cfg(feature = "cli")]
109 pub(crate) fn rust_file(mut self, path: impl Into<PathBuf>, mode: OutputMode) -> Self {
110 self.rust_file = Some(Output::new(path, mode));
111 self
112 }
113
114 #[must_use]
116 pub fn cpp(mut self, directory: impl Into<PathBuf>, mode: OutputMode) -> Self {
117 self.cpp = Some(Output::new(directory, mode));
118 self
119 }
120
121 #[must_use]
123 pub fn cpp_include_root(mut self, root: impl Into<String>) -> Self {
124 self.cpp_include_root = root.into();
125 self
126 }
127
128 #[must_use]
130 pub fn facade(mut self, facade: Facade) -> Self {
131 self.facade = Some(facade);
132 self
133 }
134
135 pub fn generate(self) -> Result<GeneratedBindings, String> {
136 let generated = self.generate_for_build()?;
137 for path in generated.rerun_paths {
138 println!("cargo:rerun-if-changed={}", path.display());
139 }
140 Ok(generated.bindings)
141 }
142
143 pub(super) fn generate_for_build(self) -> Result<BuildGeneration, String> {
144 let (bindings, inputs) =
145 generate_bindings(&self.manifest, self.facade, &self.cpp_include_root)?;
146 let mut rerun_paths = BTreeSet::from([self.manifest.clone()]);
147 rerun_paths.extend(inputs);
148 if let Some(output) = self.rust_file {
149 output.file(
150 &bindings.rust,
151 "Rust bindings",
152 &self.manifest,
153 &mut rerun_paths,
154 )?;
155 }
156 if let Some(output) = self.rust {
157 output.child("bindings.rs").file(
158 &bindings.rust,
159 "Rust bindings",
160 &self.manifest,
161 &mut rerun_paths,
162 )?;
163 output.files(
164 &bindings.rust_files,
165 "Rust ShellAPI fragment",
166 ".tuffite-rust-outputs.json",
167 &self.manifest,
168 &mut rerun_paths,
169 )?;
170 }
171 if let Some(output) = self.javascript {
172 output.child("index.js").file(
173 &bindings.javascript,
174 "JavaScript bindings",
175 &self.manifest,
176 &mut rerun_paths,
177 )?;
178 output.child("index.d.ts").file(
179 &bindings.declarations,
180 "TypeScript declarations",
181 &self.manifest,
182 &mut rerun_paths,
183 )?;
184 output.files(
185 &bindings.javascript_files,
186 "JavaScript ShellAPI module",
187 ".tuffite-javascript-outputs.json",
188 &self.manifest,
189 &mut rerun_paths,
190 )?;
191 output.files(
192 &bindings.declaration_files,
193 "TypeScript ShellAPI declaration",
194 ".tuffite-declaration-outputs.json",
195 &self.manifest,
196 &mut rerun_paths,
197 )?;
198 }
199 if let Some(output) = self.cpp {
200 output.files(
201 &bindings.cpp_files,
202 "C++ ShellAPI file",
203 ".tuffite-cpp-outputs.json",
204 &self.manifest,
205 &mut rerun_paths,
206 )?;
207 }
208 Ok(BuildGeneration {
209 bindings,
210 rerun_paths,
211 })
212 }
213}
214
215impl Default for Generator {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl Output {
222 fn new(path: impl Into<PathBuf>, mode: OutputMode) -> Self {
223 Self {
224 path: path.into(),
225 mode,
226 }
227 }
228
229 fn child(&self, name: &str) -> Self {
230 Self::new(self.path.join(name), self.mode)
231 }
232
233 fn file(
234 &self,
235 contents: &str,
236 description: &str,
237 manifest: &std::path::Path,
238 rerun_paths: &mut BTreeSet<PathBuf>,
239 ) -> Result<(), String> {
240 emit(Some(self), contents, description, manifest)?;
241 rerun_paths.insert(self.path.clone());
244 Ok(())
245 }
246
247 fn files(
248 &self,
249 files: &BTreeMap<String, String>,
250 description: &str,
251 plan_name: &str,
252 manifest: &std::path::Path,
253 rerun_paths: &mut BTreeSet<PathBuf>,
254 ) -> Result<(), String> {
255 emit_files(Some(self), files, description, plan_name, manifest)?;
256 rerun_paths.extend(files.keys().map(|name| self.path.join(name)));
257 rerun_paths.insert(self.path.join(plan_name));
258 Ok(())
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn conventional_manifest_is_in_the_cargo_package() {
268 let root = std::env::var_os("CARGO_MANIFEST_DIR")
269 .map_or_else(|| PathBuf::from("."), PathBuf::from);
270 assert_eq!(Generator::new().manifest, root.join("tuffite.json"));
271 assert_eq!(
272 Generator::new().manifest("custom.json").manifest,
273 PathBuf::from("custom.json")
274 );
275 }
276}