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 IMAGE_CLASSIFIER_CONFIG: &str = include_str!("../../templates/config/nodes/image-classifier/config.json");
32const LLM_COMMAND_CONFIG: &str = include_str!("../../templates/config/nodes/llm-command/config.json");
33const OBJECT_DETECTOR_CONFIG: &str = include_str!("../../templates/config/nodes/object-detector/config.json");
34const SIMULATION_BRIDGE_CONFIG: &str = include_str!("../../templates/config/nodes/simulation-bridge/config.json");
35const WEBSOCKET_BRIDGE_CONFIG: &str = include_str!("../../templates/config/nodes/websocket-bridge/config.json");
36
37const AIKO_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/aiko.jpg");
39const PHOEBE_IMAGE: &[u8] = include_bytes!("../../templates/assets/images/phoebe.jpg");
40const CAT_TOWER_IMAGE: &[u8] = include_bytes!("../../templates/assets/cat_tower.jpg");
41
42const BEHAVIOR_BASIC_NAVIGATION: &str = include_str!("../../templates/behaviors/basic_navigation.json");
44const BEHAVIOR_IDLE_WANDER: &str = include_str!("../../templates/behaviors/idle_wander.json");
45const BEHAVIOR_OBSTACLE_AVOIDANCE: &str = include_str!("../../templates/behaviors/obstacle_avoidance.json");
46const BEHAVIOR_PATROL_SIMPLE: &str = include_str!("../../templates/behaviors/patrol_simple.json");
47
48const SIMULATION_CONFIG_DEV: &str = include_str!("../../templates/config/simulation/dev/config.json");
50const SIMULATION_CONFIG_PRODUCTION: &str = include_str!("../../templates/config/simulation/production/config.json");
51
52const SIMULATION_DOCKERFILE: &str = include_str!("../../templates/docker/simulation/Dockerfile");
54const SIMULATION_ENTRYPOINT: &str = include_str!("../../templates/docker/simulation/entrypoint.sh");
55const SIMULATION_DOCKERIGNORE: &str = include_str!("../../templates/docker/simulation/.dockerignore");
56
57pub struct ProjectTemplateService {
71 rust: RustTemplates,
72 infra: InfraTemplates,
73 meta: MetaTemplates,
74 embedded: EmbeddedTemplates,
75}
76
77impl ProjectTemplateService {
78 pub fn new() -> Self {
80 Self {
81 rust: RustTemplates::new(),
82 infra: InfraTemplates::new(),
83 meta: MetaTemplates::new(),
84 embedded: EmbeddedTemplates::new(),
85 }
86 }
87
88 pub async fn create_readme(&self, path: &Path, project_name: &str) -> Result<()> {
90 self.meta.create_readme(path, project_name).await
91 }
92
93 pub async fn create_gitignore(&self, path: &Path) -> Result<()> {
95 self.meta.create_gitignore(path).await
96 }
97
98 pub async fn create_cargo_config(&self, path: &Path, framework_path: &str) -> Result<()> {
103 self.rust.create_cargo_config(path, framework_path).await
104 }
105
106 pub async fn create_cargo_toml(&self, path: &Path, project_name: &str, dev: bool) -> Result<()> {
114 self.rust.create_cargo_toml(path, project_name, dev).await
115 }
116
117 pub async fn create_main_rs(&self, path: &Path, project_name: &str) -> Result<()> {
119 self.rust.create_main_rs(path, project_name).await
120 }
121
122 pub async fn create_build_rs(&self, path: &Path) -> Result<()> {
128 self.rust.create_build_rs(path).await
129 }
130
131 pub async fn create_embedded_structure(&self, path: &Path) -> Result<()> {
133 self.embedded.create_structure(path).await
134 }
135
136 pub async fn create_env_example(
144 &self,
145 path: &Path,
146 project_name: &str,
147 framework_path: Option<String>,
148 ) -> Result<()> {
149 self.infra.create_env_example(path, project_name, framework_path).await
150 }
151
152 pub async fn create_rustfmt_toml(&self, path: &Path) -> Result<()> {
154 self.rust.create_rustfmt_toml(path).await
155 }
156
157 pub async fn create_docker_compose(&self, path: &Path, project_name: &str) -> Result<()> {
167 self.infra.create_docker_compose(path, project_name).await
168 }
169
170 pub async fn create_package_json(&self, path: &Path, project_name: &str) -> Result<()> {
182 self.meta.create_package_json(path, project_name).await
183 }
184
185 pub async fn create_requirements_txt(&self, path: &Path) -> Result<()> {
194 self.meta.create_requirements_txt(path).await
195 }
196
197 pub async fn create_mecha10_json(&self, path: &Path, project_name: &str, template: &Option<String>) -> Result<()> {
208 let platform = template.as_deref().unwrap_or("basic");
209 let project_id = project_name.replace('-', "_");
210
211 let config_content = MECHA10_JSON_TEMPLATE
213 .replace("{{project_name}}", project_name)
214 .replace("{{project_id}}", &project_id)
215 .replace("{{platform}}", platform);
216
217 tokio::fs::write(path.join("mecha10.json"), config_content).await?;
218 Ok(())
219 }
220
221 pub async fn create_simulation_model_json(&self, path: &Path) -> Result<()> {
230 let model_dest = path.join("simulation/models/rover/model.json");
231
232 if let Some(parent) = model_dest.parent() {
234 tokio::fs::create_dir_all(parent).await?;
235 }
236
237 tokio::fs::write(&model_dest, MODEL_JSON_TEMPLATE)
239 .await
240 .context("Failed to write model.json")?;
241
242 Ok(())
243 }
244
245 pub async fn create_simulation_environment_json(&self, path: &Path) -> Result<()> {
254 let env_dest = path.join("simulation/environments/basic_arena/environment.json");
255
256 if let Some(parent) = env_dest.parent() {
258 tokio::fs::create_dir_all(parent).await?;
259 }
260
261 tokio::fs::write(&env_dest, ENVIRONMENT_JSON_TEMPLATE)
263 .await
264 .context("Failed to write environment.json")?;
265
266 Ok(())
267 }
268
269 pub async fn create_node_configs(&self, path: &Path) -> Result<()> {
281 let node_configs: &[(&str, &str)] = &[
284 ("behavior-executor", BEHAVIOR_EXECUTOR_CONFIG),
285 ("image-classifier", IMAGE_CLASSIFIER_CONFIG),
286 ("llm-command", LLM_COMMAND_CONFIG),
287 ("object-detector", OBJECT_DETECTOR_CONFIG),
288 ("simulation-bridge", SIMULATION_BRIDGE_CONFIG),
289 ("websocket-bridge", WEBSOCKET_BRIDGE_CONFIG),
290 ];
291
292 for (node_name, config_content) in node_configs {
293 let dev_dest = path.join("configs/dev/nodes").join(node_name).join("config.json");
295
296 if let Some(parent) = dev_dest.parent() {
297 tokio::fs::create_dir_all(parent).await?;
298 }
299
300 tokio::fs::write(&dev_dest, *config_content)
301 .await
302 .with_context(|| format!("Failed to write {}/config.json", node_name))?;
303
304 let common_dest = path.join("configs/common/nodes").join(node_name).join("config.json");
306
307 if let Some(parent) = common_dest.parent() {
308 tokio::fs::create_dir_all(parent).await?;
309 }
310
311 tokio::fs::write(&common_dest, *config_content)
312 .await
313 .with_context(|| format!("Failed to write common/{}/config.json", node_name))?;
314 }
315
316 Ok(())
317 }
318
319 pub async fn create_simulation_assets(&self, path: &Path) -> Result<()> {
329 let images_dest = path.join("assets/images");
331 tokio::fs::create_dir_all(&images_dest).await?;
332
333 let image_assets: &[(&str, &[u8])] = &[("aiko.jpg", AIKO_IMAGE), ("phoebe.jpg", PHOEBE_IMAGE)];
335
336 for (filename, content) in image_assets {
337 let dest_file = images_dest.join(filename);
338 tokio::fs::write(&dest_file, content)
339 .await
340 .with_context(|| format!("Failed to write assets/images/{}", filename))?;
341 }
342
343 let assets_dest = path.join("assets");
345 tokio::fs::write(assets_dest.join("cat_tower.jpg"), CAT_TOWER_IMAGE)
346 .await
347 .context("Failed to write assets/cat_tower.jpg")?;
348
349 Ok(())
350 }
351
352 pub async fn create_behavior_templates(&self, path: &Path) -> Result<()> {
362 let behaviors_dest = path.join("behaviors");
363 tokio::fs::create_dir_all(&behaviors_dest).await?;
364
365 let behavior_templates: &[(&str, &str)] = &[
366 ("basic_navigation.json", BEHAVIOR_BASIC_NAVIGATION),
367 ("idle_wander.json", BEHAVIOR_IDLE_WANDER),
368 ("obstacle_avoidance.json", BEHAVIOR_OBSTACLE_AVOIDANCE),
369 ("patrol_simple.json", BEHAVIOR_PATROL_SIMPLE),
370 ];
371
372 for (filename, content) in behavior_templates {
373 let dest_file = behaviors_dest.join(filename);
374 tokio::fs::write(&dest_file, *content)
375 .await
376 .with_context(|| format!("Failed to write behaviors/{}", filename))?;
377 }
378
379 Ok(())
380 }
381
382 pub async fn create_simulation_configs(&self, path: &Path) -> Result<()> {
392 let dev_dest = path.join("configs/dev/simulation/config.json");
394 if let Some(parent) = dev_dest.parent() {
395 tokio::fs::create_dir_all(parent).await?;
396 }
397 tokio::fs::write(&dev_dest, SIMULATION_CONFIG_DEV)
398 .await
399 .context("Failed to write configs/dev/simulation/config.json")?;
400
401 let prod_dest = path.join("configs/production/simulation/config.json");
403 if let Some(parent) = prod_dest.parent() {
404 tokio::fs::create_dir_all(parent).await?;
405 }
406 tokio::fs::write(&prod_dest, SIMULATION_CONFIG_PRODUCTION)
407 .await
408 .context("Failed to write configs/production/simulation/config.json")?;
409
410 Ok(())
411 }
412
413 pub async fn create_simulation_docker_files(&self, path: &Path) -> Result<()> {
423 let simulation_dest = path.join("simulation");
424 tokio::fs::create_dir_all(&simulation_dest).await?;
425
426 tokio::fs::write(simulation_dest.join("Dockerfile"), SIMULATION_DOCKERFILE)
428 .await
429 .context("Failed to write simulation/Dockerfile")?;
430
431 let entrypoint_path = simulation_dest.join("entrypoint.sh");
433 tokio::fs::write(&entrypoint_path, SIMULATION_ENTRYPOINT)
434 .await
435 .context("Failed to write simulation/entrypoint.sh")?;
436
437 #[cfg(unix)]
438 {
439 use std::os::unix::fs::PermissionsExt;
440 let mut perms = tokio::fs::metadata(&entrypoint_path).await?.permissions();
441 perms.set_mode(0o755);
442 tokio::fs::set_permissions(&entrypoint_path, perms).await?;
443 }
444
445 tokio::fs::write(simulation_dest.join(".dockerignore"), SIMULATION_DOCKERIGNORE)
447 .await
448 .context("Failed to write simulation/.dockerignore")?;
449
450 Ok(())
451 }
452}
453
454impl Default for ProjectTemplateService {
455 fn default() -> Self {
456 Self::new()
457 }
458}