use crate::{ commands::CreateArgs, entity::project::ProjectInfo, error::Result, template::TemplateEngine, }; use std::{fs, path::PathBuf}; use uuid::Uuid; include!(concat!(env!("OUT_DIR"), "/embedded_examples.rs")); pub fn execute(args: &CreateArgs) { if let Err(e) = create_project(&args.name, args.target.as_deref()) { eprintln!("Error: {}", e); return; } println!("Success: project created."); } fn create_project(name: &str, target: Option<&str>) -> Result<()> { let target = target.unwrap_or("default"); check_example_exists(target)?; let local_dir = PathBuf::from(format!("./{}", name)); fs::create_dir(&local_dir)?; copy_embedded_template(target, &local_dir)?; initialize_project_with_template(&local_dir, &local_dir, name)?; Ok(()) } fn check_example_exists(target: &str) -> Result<()> { if !embedded_example_exists(target) { return Err(crate::error::CliError::NotFound(format!( "example target '{}' was not found in the built-in examples", target ))); } Ok(()) } fn embedded_example_exists(target: &str) -> bool { let target = normalize_embedded_path(target); let prefix = format!("{}/", target); EMBEDDED_EXAMPLE_DIRS .iter() .any(|dir| *dir == target || dir.starts_with(&prefix)) || EMBEDDED_EXAMPLE_FILES .iter() .any(|file| file.path.starts_with(&prefix)) } fn copy_embedded_template(target: &str, local_dir: &PathBuf) -> Result<()> { let target = normalize_embedded_path(target); let prefix = format!("{}/", target); for dir in EMBEDDED_EXAMPLE_DIRS { if let Some(relative_path) = dir.strip_prefix(&prefix) { if !relative_path.is_empty() { fs::create_dir_all(local_dir.join(relative_path))?; } } } for file in EMBEDDED_EXAMPLE_FILES { let Some(relative_path) = file.path.strip_prefix(&prefix) else { continue; }; let output_path = local_dir.join(relative_path); if let Some(parent) = output_path.parent() { fs::create_dir_all(parent)?; } fs::write(output_path, file.contents)?; } Ok(()) } fn normalize_embedded_path(path: &str) -> String { path.trim_matches(['/', '\\']).replace('\\', "/") } fn initialize_project_with_template( template_dir: &PathBuf, local_dir: &PathBuf, name: &str, ) -> Result<()> { let lower_name = lower_first_char(name)?; println!("Project name: {}", name); println!("Lower project name: {}", lower_name); let project_info = generate_project_info(name, &lower_name); let mut engine = TemplateEngine::load(template_dir)?; engine.set_variable("mod_name".to_string(), project_info.name.clone()); engine.set_variable( "mod_name_lower".to_string(), project_info.lower_name.clone(), ); engine.set_variable( "behavior_pack_uuid".to_string(), project_info.behavior_pack_uuid.clone(), ); engine.set_variable( "resource_pack_uuid".to_string(), project_info.resource_pack_uuid.clone(), ); engine.set_variable( "behavior_module_uuid".to_string(), project_info.behavior_module_uuid.clone(), ); engine.set_variable( "resource_module_uuid".to_string(), project_info.resource_module_uuid.clone(), ); engine.set_variable( "behavior_pack_uuid_short".to_string(), project_info.behavior_pack_uuid.chars().take(8).collect(), ); engine.set_variable( "resource_pack_uuid_short".to_string(), project_info.resource_pack_uuid.chars().take(8).collect(), ); engine.process_directory(local_dir)?; remove_template_config(local_dir)?; Ok(()) } fn remove_template_config(local_dir: &PathBuf) -> Result<()> { let template_config = local_dir.join("template.toml"); if template_config.exists() { fs::remove_file(template_config)?; } Ok(()) } fn lower_first_char(name: &str) -> Result { let mut chars = name.chars(); let Some(first) = chars.next() else { return Err(crate::error::CliError::InvalidInput( "project name cannot be empty".to_string(), )); }; Ok(format!("{}{}", first.to_lowercase(), chars.as_str())) } fn generate_project_info(name: &str, lower_name: &str) -> ProjectInfo { ProjectInfo { name: name.to_string(), lower_name: lower_name.to_string(), behavior_pack_uuid: Uuid::new_v4().to_string(), resource_pack_uuid: Uuid::new_v4().to_string(), behavior_module_uuid: Uuid::new_v4().to_string(), resource_module_uuid: Uuid::new_v4().to_string(), } }