1use std::path::Path;
5pub mod executer;
6pub mod template;
7use std::sync::Arc;
8
9use include_dir::{include_dir, Dir};
10use rhai::{
11 export_module, exported_module,
12 plugin::{
13 Dynamic, FnNamespace, FuncRegistration, Module, NativeCallContext, PluginFunc, RhaiResult,
14 TypeId,
15 },
16 Engine, Scope,
17};
18
19use crate::wizard::AssetsOption;
20use crate::{settings, OS};
21
22static APP_TEMPLATE: Dir<'_> = include_dir!("base_template");
23
24pub fn extract_default_template() -> std::io::Result<tree_fs::Tree> {
30 let generator_tmp_folder = tree_fs::TreeBuilder::default().create()?;
31
32 APP_TEMPLATE.extract(&generator_tmp_folder.root)?;
33 Ok(generator_tmp_folder)
34}
35
36#[derive(Clone)]
40pub struct Generator {
41 pub executer: Arc<dyn executer::Executer>,
42 pub settings: settings::Settings,
43}
44impl Generator {
45 pub fn new(executer: Arc<dyn executer::Executer>, settings: settings::Settings) -> Self {
47 Self { executer, settings }
48 }
49
50 pub fn run(&self) -> crate::Result<()> {
56 self.run_from_script(include_str!("../../setup.rhai"))
57 }
58
59 pub fn run_from_script(&self, script: &str) -> crate::Result<()> {
65 let mut engine = Engine::new();
66
67 tracing::debug!(
68 settings = format!("{:?}", self.settings),
69 script,
70 "prepare installation script"
71 );
72 engine
73 .build_type::<settings::Settings>()
74 .build_type::<settings::Initializers>()
75 .build_type::<settings::Db>()
76 .build_type::<settings::Asset>()
77 .build_type::<settings::Background>()
78 .register_static_module(
79 "rhai_settings_extensions",
80 exported_module!(rhai_settings_extensions).into(),
81 )
82 .register_fn("copy_file", Self::copy_file)
83 .register_fn("create_file", Self::create_file)
84 .register_fn("copy_files", Self::copy_files)
85 .register_fn("copy_dir", Self::copy_dir)
86 .register_fn("copy_dirs", Self::copy_dirs)
87 .register_fn("copy_template", Self::copy_template)
88 .register_fn("copy_template_dir", Self::copy_template_dir);
89
90 let settings_dynamic = rhai::Dynamic::from(self.settings.clone());
91
92 let mut scope = Scope::new();
93 scope.set_value("settings", settings_dynamic);
94 scope.push("gen", self.clone());
95 scope.push("db", self.settings.db.is_some());
97 scope.push("background", self.settings.background.is_some());
98 scope.push("initializers", self.settings.initializers.is_some());
99 scope.push("asset", self.settings.asset.is_some());
100 scope.push("windows", self.settings.os == OS::Windows);
101
102 engine.run_with_scope(&mut scope, script)?;
103 Ok(())
104 }
105
106 pub fn copy_file(&mut self, path: &str) -> Result<(), Box<rhai::EvalAltResult>> {
112 let span = tracing::info_span!("copy_file", path);
113 let _guard = span.enter();
114
115 self.executer.copy_file(Path::new(path)).map_err(|err| {
116 Box::new(rhai::EvalAltResult::ErrorSystem(
117 "copy_file".to_string(),
118 err.into(),
119 ))
120 })?;
121 Ok(())
122 }
123
124 pub fn create_file(
130 &mut self,
131 path: &str,
132 content: &str,
133 ) -> Result<(), Box<rhai::EvalAltResult>> {
134 let span = tracing::info_span!("create_file", path);
135 let _guard = span.enter();
136
137 self.executer
138 .create_file(Path::new(path), content.to_string())
139 .map_err(|err| {
140 Box::new(rhai::EvalAltResult::ErrorSystem(
141 "create_file".to_string(),
142 err.into(),
143 ))
144 })?;
145 Ok(())
146 }
147
148 pub fn copy_files(&mut self, paths: rhai::Array) -> Result<(), Box<rhai::EvalAltResult>> {
154 let span = tracing::info_span!("copy_files");
155 let _guard = span.enter();
156 for path in paths {
157 self.executer
158 .copy_file(Path::new(&path.to_string()))
159 .map_err(|err| {
160 Box::new(rhai::EvalAltResult::ErrorSystem(
161 "copy_files".to_string(),
162 err.into(),
163 ))
164 })?;
165 }
166
167 Ok(())
168 }
169
170 pub fn copy_dir(&mut self, path: &str) -> Result<(), Box<rhai::EvalAltResult>> {
176 let span = tracing::info_span!("copy_dir", path);
177 let _guard = span.enter();
178 self.executer.copy_dir(Path::new(path)).map_err(|err| {
179 Box::new(rhai::EvalAltResult::ErrorSystem(
180 "copy_dir".to_string(),
181 err.into(),
182 ))
183 })
184 }
185
186 pub fn copy_dirs(&mut self, paths: rhai::Array) -> Result<(), Box<rhai::EvalAltResult>> {
192 let span = tracing::info_span!("copy_dirs");
193 let _guard = span.enter();
194 for path in paths {
195 self.executer
196 .copy_dir(Path::new(&path.to_string()))
197 .map_err(|err| {
198 Box::new(rhai::EvalAltResult::ErrorSystem(
199 "copy_dirs".to_string(),
200 err.into(),
201 ))
202 })?;
203 }
204 Ok(())
205 }
206
207 pub fn copy_template(&mut self, path: &str) -> Result<(), Box<rhai::EvalAltResult>> {
213 let span = tracing::info_span!("copy_template", path);
214 let _guard = span.enter();
215 self.executer
216 .copy_template(Path::new(path), &self.settings)
217 .map_err(|err| {
218 Box::new(rhai::EvalAltResult::ErrorSystem(
219 "copy_template".to_string(),
220 err.into(),
221 ))
222 })
223 }
224
225 pub fn copy_template_dir(&mut self, path: &str) -> Result<(), Box<rhai::EvalAltResult>> {
232 let span = tracing::info_span!("copy_template_dir", path);
233 let _guard = span.enter();
234 self.executer
235 .copy_template_dir(Path::new(path), &self.settings)
236 .map_err(|err| {
237 Box::new(rhai::EvalAltResult::ErrorSystem(
238 "copy_template_dir".to_string(),
239 err.into(),
240 ))
241 })
242 }
243}
244
245#[export_module]
250mod rhai_settings_extensions {
251 #[rhai_fn(global, get = "is_client_side", pure)]
253 pub const fn is_client_side(rendering_method: &mut settings::Asset) -> bool {
254 matches!(rendering_method.kind, AssetsOption::Clientside)
255 }
256
257 #[rhai_fn(global, get = "is_server_side", pure)]
259 pub const fn is_server_side(rendering_method: &mut settings::Asset) -> bool {
260 matches!(rendering_method.kind, AssetsOption::Serverside)
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use executer::MockExecuter;
267 use mockall::predicate::*;
268
269 use super::*;
270
271 #[test]
272 pub fn can_copy_file() {
273 let mut executor = MockExecuter::new();
274
275 executor
276 .expect_copy_file()
277 .with(eq(Path::new("test.rs")))
278 .times(1)
279 .returning(|p| Ok(p.to_path_buf()));
280
281 let g = Generator::new(Arc::new(executor), settings::Settings::default());
282 let script_res = g.run_from_script(r#"gen.copy_file("test.rs");"#);
283
284 assert!(script_res.is_ok());
285 }
286
287 #[test]
288 pub fn can_copy_files() {
289 let mut executor = MockExecuter::new();
290
291 executor
292 .expect_copy_file()
293 .with(eq(Path::new(".gitignore")))
294 .times(1)
295 .returning(|p| Ok(p.to_path_buf()));
296
297 executor
298 .expect_copy_file()
299 .with(eq(Path::new(".rustfmt.toml")))
300 .times(1)
301 .returning(|p| Ok(p.to_path_buf()));
302
303 executor
304 .expect_copy_file()
305 .with(eq(Path::new("README.md")))
306 .times(1)
307 .returning(|p| Ok(p.to_path_buf()));
308
309 let g = Generator::new(Arc::new(executor), settings::Settings::default());
310 let script_res =
311 g.run_from_script(r#"gen.copy_files([".gitignore", ".rustfmt.toml", "README.md"]);"#);
312
313 assert!(script_res.is_ok());
314 }
315
316 #[test]
317 pub fn can_copy_dir() {
318 let mut executor = MockExecuter::new();
319
320 executor
321 .expect_copy_dir()
322 .with(eq(Path::new("test")))
323 .times(1)
324 .returning(|_| Ok(()));
325
326 let g = Generator::new(Arc::new(executor), settings::Settings::default());
327 let script_res = g.run_from_script(r#"gen.copy_dir("test");"#);
328
329 assert!(script_res.is_ok());
330 }
331
332 #[test]
333 pub fn can_copy_dirs() {
334 let mut executor = MockExecuter::new();
335
336 executor
337 .expect_copy_dir()
338 .with(eq(Path::new("src")))
339 .times(1)
340 .returning(|_| Ok(()));
341
342 executor
343 .expect_copy_dir()
344 .with(eq(Path::new("example")))
345 .times(1)
346 .returning(|_| Ok(()));
347
348 executor
349 .expect_copy_dir()
350 .with(eq(Path::new(".github")))
351 .times(1)
352 .returning(|_| Ok(()));
353
354 let g = Generator::new(Arc::new(executor), settings::Settings::default());
355 let script_res = g.run_from_script(r#"gen.copy_dirs(["src", "example", ".github"]);"#);
356
357 assert!(script_res.is_ok());
358 }
359
360 #[test]
361 pub fn can_copy_template() {
362 let mut executor = MockExecuter::new();
363
364 executor
365 .expect_copy_template()
366 .with(eq(Path::new("src/lib.rs.t")), always())
367 .times(1)
368 .returning(|_, _| Ok(()));
369
370 let g = Generator::new(Arc::new(executor), settings::Settings::default());
371 let script_res = g.run_from_script(r#"gen.copy_template("src/lib.rs.t");"#);
372
373 assert!(script_res.is_ok());
374 }
375
376 #[test]
377 pub fn can_copy_template_dir() {
378 let mut executor = MockExecuter::new();
379
380 executor
381 .expect_copy_template_dir()
382 .with(eq(Path::new("src/examples")), always())
383 .times(1)
384 .returning(|_, _| Ok(()));
385
386 let g = Generator::new(Arc::new(executor), settings::Settings::default());
387 let script_res = g.run_from_script(r#"gen.copy_template_dir("src/examples");"#);
388
389 assert!(script_res.is_ok());
390 }
391}