1use crate::templates::{EmbeddedTemplates, InfraTemplates, MetaTemplates, RustTemplates};
21use anyhow::{Context, Result};
22use std::path::Path;
23
24const MECHA10_JSON_TEMPLATE: &str = include_str!("../../templates/config/mecha10.json.template");
26const MODEL_JSON_TEMPLATE: &str = include_str!("../../templates/config/model.json.template");
27const ENVIRONMENT_JSON_TEMPLATE: &str = include_str!("../../templates/config/environment.json.template");
28
29const BEHAVIOR_EXECUTOR_CONFIG_DEV: &str =
31 include_str!("../../templates/config/nodes/behavior-executor/dev/config.json");
32const IMAGE_CLASSIFIER_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/image-classifier/dev/config.json");
33const LLM_COMMAND_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/llm-command/dev/config.json");
34const OBJECT_DETECTOR_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/object-detector/dev/config.json");
35const SIMULATION_BRIDGE_CONFIG_DEV: &str =
36 include_str!("../../templates/config/nodes/simulation-bridge/dev/config.json");
37const WEBSOCKET_BRIDGE_CONFIG_DEV: &str = include_str!("../../templates/config/nodes/websocket-bridge/dev/config.json");
38
39const SIMULATION_CONFIG_DEV: &str = include_str!("../../templates/config/simulation/dev/config.json");
41const SIMULATION_CONFIG_PRODUCTION: &str = include_str!("../../templates/config/simulation/production/config.json");
42
43const AIKO_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/aiko.jpg");
45const PHOEBE_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/phoebe.jpg");
46const CAT_TOWER_IMAGE: &[u8] = include_bytes!("../../templates/assets/cat_tower.jpg");
47
48const BEHAVIOR_BASIC_NAVIGATION: &str = include_str!("../../templates/behaviors/basic_navigation.json");
50const BEHAVIOR_IDLE_WANDER: &str = include_str!("../../templates/behaviors/idle_wander.json");
51const BEHAVIOR_OBSTACLE_AVOIDANCE: &str = include_str!("../../templates/behaviors/obstacle_avoidance.json");
52const BEHAVIOR_PATROL_SIMPLE: &str = include_str!("../../templates/behaviors/patrol_simple.json");
53
54const SIMULATION_DOCKERFILE: &str = include_str!("../../templates/docker/simulation/Dockerfile");
56const SIMULATION_ENTRYPOINT: &str = include_str!("../../templates/docker/simulation/entrypoint.sh");
57const SIMULATION_DOCKERIGNORE: &str = include_str!("../../templates/docker/simulation/.dockerignore");
58
59pub struct ProjectTemplateService {
73 rust: RustTemplates,
74 infra: InfraTemplates,
75 meta: MetaTemplates,
76 embedded: EmbeddedTemplates,
77}
78
79impl ProjectTemplateService {
80 pub fn new() -> Self {
82 Self {
83 rust: RustTemplates::new(),
84 infra: InfraTemplates::new(),
85 meta: MetaTemplates::new(),
86 embedded: EmbeddedTemplates::new(),
87 }
88 }
89
90 pub async fn create_readme(&self, path: &Path, project_name: &str) -> Result<()> {
92 self.meta.create_readme(path, project_name).await
93 }
94
95 pub async fn create_gitignore(&self, path: &Path) -> Result<()> {
97 self.meta.create_gitignore(path).await
98 }
99
100 pub async fn create_cargo_config(&self, path: &Path, framework_path: &str) -> Result<()> {
105 self.rust.create_cargo_config(path, framework_path).await
106 }
107
108 pub async fn create_cargo_toml(&self, path: &Path, project_name: &str, dev: bool) -> Result<()> {
116 self.rust.create_cargo_toml(path, project_name, dev).await
117 }
118
119 pub async fn create_main_rs(&self, path: &Path, project_name: &str) -> Result<()> {
121 self.rust.create_main_rs(path, project_name).await
122 }
123
124 pub async fn create_build_rs(&self, path: &Path) -> Result<()> {
130 self.rust.create_build_rs(path).await
131 }
132
133 pub async fn create_embedded_structure(&self, path: &Path) -> Result<()> {
135 self.embedded.create_structure(path).await
136 }
137
138 pub async fn create_env_example(
146 &self,
147 path: &Path,
148 project_name: &str,
149 framework_path: Option<String>,
150 ) -> Result<()> {
151 self.infra.create_env_example(path, project_name, framework_path).await
152 }
153
154 pub async fn create_rustfmt_toml(&self, path: &Path) -> Result<()> {
156 self.rust.create_rustfmt_toml(path).await
157 }
158
159 pub async fn create_docker_compose(&self, path: &Path, project_name: &str) -> Result<()> {
169 self.infra.create_docker_compose(path, project_name).await
170 }
171
172 pub async fn create_package_json(&self, path: &Path, project_name: &str) -> Result<()> {
184 self.meta.create_package_json(path, project_name).await
185 }
186
187 pub async fn create_requirements_txt(&self, path: &Path) -> Result<()> {
196 self.meta.create_requirements_txt(path).await
197 }
198
199 pub async fn create_mecha10_json(&self, path: &Path, project_name: &str, template: &Option<String>) -> Result<()> {
210 let platform = template.as_deref().unwrap_or("basic");
211 let project_id = project_name.replace('-', "_");
212
213 let config_content = MECHA10_JSON_TEMPLATE
215 .replace("{{project_name}}", project_name)
216 .replace("{{project_id}}", &project_id)
217 .replace("{{platform}}", platform);
218
219 tokio::fs::write(path.join("mecha10.json"), config_content).await?;
220 Ok(())
221 }
222
223 pub async fn create_simulation_model_json(&self, path: &Path) -> Result<()> {
232 let model_dest = path.join("simulation/models/rover/model.json");
233
234 if let Some(parent) = model_dest.parent() {
236 tokio::fs::create_dir_all(parent).await?;
237 }
238
239 tokio::fs::write(&model_dest, MODEL_JSON_TEMPLATE)
241 .await
242 .context("Failed to write model.json")?;
243
244 Ok(())
245 }
246
247 pub async fn create_simulation_environment_json(&self, path: &Path) -> Result<()> {
256 let env_dest = path.join("simulation/environments/basic_arena/environment.json");
257
258 if let Some(parent) = env_dest.parent() {
260 tokio::fs::create_dir_all(parent).await?;
261 }
262
263 tokio::fs::write(&env_dest, ENVIRONMENT_JSON_TEMPLATE)
265 .await
266 .context("Failed to write environment.json")?;
267
268 Ok(())
269 }
270
271 pub async fn create_node_configs(&self, path: &Path) -> Result<()> {
280 let node_configs: &[(&str, &str)] = &[
281 ("behavior-executor", BEHAVIOR_EXECUTOR_CONFIG_DEV),
282 ("image-classifier", IMAGE_CLASSIFIER_CONFIG_DEV),
283 ("llm-command", LLM_COMMAND_CONFIG_DEV),
284 ("object-detector", OBJECT_DETECTOR_CONFIG_DEV),
285 ("simulation-bridge", SIMULATION_BRIDGE_CONFIG_DEV),
286 ("websocket-bridge", WEBSOCKET_BRIDGE_CONFIG_DEV),
287 ];
288
289 for (node_name, config_content) in node_configs {
290 let dev_dest = path.join("configs/dev/nodes").join(node_name).join("config.json");
291
292 if let Some(parent) = dev_dest.parent() {
293 tokio::fs::create_dir_all(parent).await?;
294 }
295
296 tokio::fs::write(&dev_dest, *config_content)
297 .await
298 .with_context(|| format!("Failed to write {}/config.json", node_name))?;
299 }
300
301 Ok(())
302 }
303
304 pub async fn create_simulation_configs(&self, path: &Path) -> Result<()> {
313 let dev_dest = path.join("configs/dev/simulation/config.json");
315 if let Some(parent) = dev_dest.parent() {
316 tokio::fs::create_dir_all(parent).await?;
317 }
318 tokio::fs::write(&dev_dest, SIMULATION_CONFIG_DEV)
319 .await
320 .context("Failed to write configs/dev/simulation/config.json")?;
321
322 let prod_dest = path.join("configs/production/simulation/config.json");
324 if let Some(parent) = prod_dest.parent() {
325 tokio::fs::create_dir_all(parent).await?;
326 }
327 tokio::fs::write(&prod_dest, SIMULATION_CONFIG_PRODUCTION)
328 .await
329 .context("Failed to write configs/production/simulation/config.json")?;
330
331 Ok(())
332 }
333
334 pub async fn create_simulation_assets(&self, path: &Path) -> Result<()> {
344 let images_dest = path.join("assets/images");
346 tokio::fs::create_dir_all(&images_dest).await?;
347
348 let image_assets: &[(&str, &[u8])] = &[("aiko.jpg", AIKO_IMAGE), ("phoebe.jpg", PHOEBE_IMAGE)];
350
351 for (filename, content) in image_assets {
352 let dest_file = images_dest.join(filename);
353 tokio::fs::write(&dest_file, content)
354 .await
355 .with_context(|| format!("Failed to write assets/images/{}", filename))?;
356 }
357
358 let assets_dest = path.join("assets");
360 tokio::fs::write(assets_dest.join("cat_tower.jpg"), CAT_TOWER_IMAGE)
361 .await
362 .context("Failed to write assets/cat_tower.jpg")?;
363
364 Ok(())
365 }
366
367 pub async fn create_behavior_templates(&self, path: &Path) -> Result<()> {
377 let behaviors_dest = path.join("behaviors");
378 tokio::fs::create_dir_all(&behaviors_dest).await?;
379
380 let behavior_templates: &[(&str, &str)] = &[
381 ("basic_navigation.json", BEHAVIOR_BASIC_NAVIGATION),
382 ("idle_wander.json", BEHAVIOR_IDLE_WANDER),
383 ("obstacle_avoidance.json", BEHAVIOR_OBSTACLE_AVOIDANCE),
384 ("patrol_simple.json", BEHAVIOR_PATROL_SIMPLE),
385 ];
386
387 for (filename, content) in behavior_templates {
388 let dest_file = behaviors_dest.join(filename);
389 tokio::fs::write(&dest_file, *content)
390 .await
391 .with_context(|| format!("Failed to write behaviors/{}", filename))?;
392 }
393
394 Ok(())
395 }
396
397 pub async fn create_simulation_docker_files(&self, path: &Path) -> Result<()> {
407 let simulation_dest = path.join("simulation");
408 tokio::fs::create_dir_all(&simulation_dest).await?;
409
410 tokio::fs::write(simulation_dest.join("Dockerfile"), SIMULATION_DOCKERFILE)
412 .await
413 .context("Failed to write simulation/Dockerfile")?;
414
415 let entrypoint_path = simulation_dest.join("entrypoint.sh");
417 tokio::fs::write(&entrypoint_path, SIMULATION_ENTRYPOINT)
418 .await
419 .context("Failed to write simulation/entrypoint.sh")?;
420
421 #[cfg(unix)]
422 {
423 use std::os::unix::fs::PermissionsExt;
424 let mut perms = tokio::fs::metadata(&entrypoint_path).await?.permissions();
425 perms.set_mode(0o755);
426 tokio::fs::set_permissions(&entrypoint_path, perms).await?;
427 }
428
429 tokio::fs::write(simulation_dest.join(".dockerignore"), SIMULATION_DOCKERIGNORE)
431 .await
432 .context("Failed to write simulation/.dockerignore")?;
433
434 Ok(())
435 }
436}
437
438impl Default for ProjectTemplateService {
439 fn default() -> Self {
440 Self::new()
441 }
442}