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: &str = include_str!("../../templates/config/nodes/behavior-executor/config.json");
31const LLM_COMMAND_CONFIG: &str = include_str!("../../templates/config/nodes/llm-command/config.json");
32const SIMULATION_BRIDGE_CONFIG: &str = include_str!("../../templates/config/nodes/simulation-bridge/config.json");
33const WEBSOCKET_BRIDGE_CONFIG: &str = include_str!("../../templates/config/nodes/websocket-bridge/config.json");
34
35const AIKO_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/aiko.jpg");
37const PHOEBE_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/phoebe.jpg");
38const CAT_TOWER_IMAGE: &[u8] = include_bytes!("../../templates/assets/cat_tower.jpg");
39
40const BEHAVIOR_BASIC_NAVIGATION: &str = include_str!("../../templates/behaviors/basic_navigation.json");
42const BEHAVIOR_IDLE_WANDER: &str = include_str!("../../templates/behaviors/idle_wander.json");
43const BEHAVIOR_OBSTACLE_AVOIDANCE: &str = include_str!("../../templates/behaviors/obstacle_avoidance.json");
44const BEHAVIOR_PATROL_SIMPLE: &str = include_str!("../../templates/behaviors/patrol_simple.json");
45
46const SIMULATION_CONFIG_DEV: &str = include_str!("../../templates/config/simulation/dev/config.json");
48const SIMULATION_CONFIG_PRODUCTION: &str = include_str!("../../templates/config/simulation/production/config.json");
49
50const SIMULATION_DOCKERFILE: &str = include_str!("../../templates/docker/simulation/Dockerfile");
52const SIMULATION_ENTRYPOINT: &str = include_str!("../../templates/docker/simulation/entrypoint.sh");
53const SIMULATION_DOCKERIGNORE: &str = include_str!("../../templates/docker/simulation/.dockerignore");
54
55pub struct ProjectTemplateService {
69 rust: RustTemplates,
70 infra: InfraTemplates,
71 meta: MetaTemplates,
72 embedded: EmbeddedTemplates,
73}
74
75impl ProjectTemplateService {
76 pub fn new() -> Self {
78 Self {
79 rust: RustTemplates::new(),
80 infra: InfraTemplates::new(),
81 meta: MetaTemplates::new(),
82 embedded: EmbeddedTemplates::new(),
83 }
84 }
85
86 pub async fn create_readme(&self, path: &Path, project_name: &str) -> Result<()> {
88 self.meta.create_readme(path, project_name).await
89 }
90
91 pub async fn create_gitignore(&self, path: &Path) -> Result<()> {
93 self.meta.create_gitignore(path).await
94 }
95
96 pub async fn create_cargo_config(&self, path: &Path, framework_path: &str) -> Result<()> {
101 self.rust.create_cargo_config(path, framework_path).await
102 }
103
104 pub async fn create_cargo_toml(&self, path: &Path, project_name: &str, dev: bool) -> Result<()> {
112 self.rust.create_cargo_toml(path, project_name, dev).await
113 }
114
115 pub async fn create_main_rs(&self, path: &Path, project_name: &str) -> Result<()> {
117 self.rust.create_main_rs(path, project_name).await
118 }
119
120 pub async fn create_build_rs(&self, path: &Path) -> Result<()> {
126 self.rust.create_build_rs(path).await
127 }
128
129 pub async fn create_embedded_structure(&self, path: &Path) -> Result<()> {
131 self.embedded.create_structure(path).await
132 }
133
134 pub async fn create_env_example(
142 &self,
143 path: &Path,
144 project_name: &str,
145 framework_path: Option<String>,
146 ) -> Result<()> {
147 self.infra.create_env_example(path, project_name, framework_path).await
148 }
149
150 pub async fn create_rustfmt_toml(&self, path: &Path) -> Result<()> {
152 self.rust.create_rustfmt_toml(path).await
153 }
154
155 pub async fn create_docker_compose(&self, path: &Path, project_name: &str) -> Result<()> {
165 self.infra.create_docker_compose(path, project_name).await
166 }
167
168 pub async fn create_package_json(&self, path: &Path, project_name: &str) -> Result<()> {
180 self.meta.create_package_json(path, project_name).await
181 }
182
183 pub async fn create_requirements_txt(&self, path: &Path) -> Result<()> {
192 self.meta.create_requirements_txt(path).await
193 }
194
195 pub async fn create_mecha10_json(&self, path: &Path, project_name: &str, template: &Option<String>) -> Result<()> {
206 let platform = template.as_deref().unwrap_or("basic");
207 let project_id = project_name.replace('-', "_");
208
209 let config_content = MECHA10_JSON_TEMPLATE
211 .replace("{{project_name}}", project_name)
212 .replace("{{project_id}}", &project_id)
213 .replace("{{platform}}", platform);
214
215 tokio::fs::write(path.join("mecha10.json"), config_content).await?;
216 Ok(())
217 }
218
219 pub async fn create_simulation_model_json(&self, path: &Path) -> Result<()> {
228 let model_dest = path.join("simulation/models/rover/model.json");
229
230 if let Some(parent) = model_dest.parent() {
232 tokio::fs::create_dir_all(parent).await?;
233 }
234
235 tokio::fs::write(&model_dest, MODEL_JSON_TEMPLATE)
237 .await
238 .context("Failed to write model.json")?;
239
240 Ok(())
241 }
242
243 pub async fn create_simulation_environment_json(&self, path: &Path) -> Result<()> {
252 let env_dest = path.join("simulation/environments/basic_arena/environment.json");
253
254 if let Some(parent) = env_dest.parent() {
256 tokio::fs::create_dir_all(parent).await?;
257 }
258
259 tokio::fs::write(&env_dest, ENVIRONMENT_JSON_TEMPLATE)
261 .await
262 .context("Failed to write environment.json")?;
263
264 Ok(())
265 }
266
267 pub async fn create_node_configs(&self, path: &Path) -> Result<()> {
279 let node_configs: &[(&str, &str)] = &[
282 ("behavior-executor", BEHAVIOR_EXECUTOR_CONFIG),
283 ("llm-command", LLM_COMMAND_CONFIG),
284 ("simulation-bridge", SIMULATION_BRIDGE_CONFIG),
285 ("websocket-bridge", WEBSOCKET_BRIDGE_CONFIG),
286 ];
287
288 for (node_name, config_content) in node_configs {
289 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 let common_dest = path.join("configs/common/nodes").join(node_name).join("config.json");
302
303 if let Some(parent) = common_dest.parent() {
304 tokio::fs::create_dir_all(parent).await?;
305 }
306
307 tokio::fs::write(&common_dest, *config_content)
308 .await
309 .with_context(|| format!("Failed to write common/{}/config.json", node_name))?;
310 }
311
312 Ok(())
313 }
314
315 pub async fn create_simulation_assets(&self, path: &Path) -> Result<()> {
325 let images_dest = path.join("assets/images");
327 tokio::fs::create_dir_all(&images_dest).await?;
328
329 let image_assets: &[(&str, &[u8])] = &[("aiko.jpg", AIKO_IMAGE), ("phoebe.jpg", PHOEBE_IMAGE)];
331
332 for (filename, content) in image_assets {
333 let dest_file = images_dest.join(filename);
334 tokio::fs::write(&dest_file, content)
335 .await
336 .with_context(|| format!("Failed to write assets/images/{}", filename))?;
337 }
338
339 let assets_dest = path.join("assets");
341 tokio::fs::write(assets_dest.join("cat_tower.jpg"), CAT_TOWER_IMAGE)
342 .await
343 .context("Failed to write assets/cat_tower.jpg")?;
344
345 Ok(())
346 }
347
348 pub async fn create_behavior_templates(&self, path: &Path) -> Result<()> {
358 let behaviors_dest = path.join("behaviors");
359 tokio::fs::create_dir_all(&behaviors_dest).await?;
360
361 let behavior_templates: &[(&str, &str)] = &[
362 ("basic_navigation.json", BEHAVIOR_BASIC_NAVIGATION),
363 ("idle_wander.json", BEHAVIOR_IDLE_WANDER),
364 ("obstacle_avoidance.json", BEHAVIOR_OBSTACLE_AVOIDANCE),
365 ("patrol_simple.json", BEHAVIOR_PATROL_SIMPLE),
366 ];
367
368 for (filename, content) in behavior_templates {
369 let dest_file = behaviors_dest.join(filename);
370 tokio::fs::write(&dest_file, *content)
371 .await
372 .with_context(|| format!("Failed to write behaviors/{}", filename))?;
373 }
374
375 Ok(())
376 }
377
378 pub async fn create_simulation_configs(&self, path: &Path) -> Result<()> {
388 let dev_dest = path.join("configs/dev/simulation/config.json");
390 if let Some(parent) = dev_dest.parent() {
391 tokio::fs::create_dir_all(parent).await?;
392 }
393 tokio::fs::write(&dev_dest, SIMULATION_CONFIG_DEV)
394 .await
395 .context("Failed to write configs/dev/simulation/config.json")?;
396
397 let prod_dest = path.join("configs/production/simulation/config.json");
399 if let Some(parent) = prod_dest.parent() {
400 tokio::fs::create_dir_all(parent).await?;
401 }
402 tokio::fs::write(&prod_dest, SIMULATION_CONFIG_PRODUCTION)
403 .await
404 .context("Failed to write configs/production/simulation/config.json")?;
405
406 Ok(())
407 }
408
409 pub async fn create_simulation_docker_files(&self, path: &Path) -> Result<()> {
419 let simulation_dest = path.join("simulation");
420 tokio::fs::create_dir_all(&simulation_dest).await?;
421
422 tokio::fs::write(simulation_dest.join("Dockerfile"), SIMULATION_DOCKERFILE)
424 .await
425 .context("Failed to write simulation/Dockerfile")?;
426
427 let entrypoint_path = simulation_dest.join("entrypoint.sh");
429 tokio::fs::write(&entrypoint_path, SIMULATION_ENTRYPOINT)
430 .await
431 .context("Failed to write simulation/entrypoint.sh")?;
432
433 #[cfg(unix)]
434 {
435 use std::os::unix::fs::PermissionsExt;
436 let mut perms = tokio::fs::metadata(&entrypoint_path).await?.permissions();
437 perms.set_mode(0o755);
438 tokio::fs::set_permissions(&entrypoint_path, perms).await?;
439 }
440
441 tokio::fs::write(simulation_dest.join(".dockerignore"), SIMULATION_DOCKERIGNORE)
443 .await
444 .context("Failed to write simulation/.dockerignore")?;
445
446 Ok(())
447 }
448}
449
450impl Default for ProjectTemplateService {
451 fn default() -> Self {
452 Self::new()
453 }
454}