2025-11-29 17:31:00 +08:00
|
|
|
use crate::{
|
2026-04-26 20:48:26 +08:00
|
|
|
commands::CreateArgs, entity::project::ProjectInfo, error::Result, template::TemplateEngine,
|
2025-11-29 17:31:00 +08:00
|
|
|
};
|
|
|
|
|
use std::{fs, path::PathBuf};
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
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);
|
2025-11-29 17:31:00 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-04-26 20:48:26 +08:00
|
|
|
println!("Success: project created.");
|
2025-11-29 17:31:00 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
fn create_project(name: &str, target: Option<&str>) -> Result<()> {
|
2025-11-29 17:31:00 +08:00
|
|
|
let target = target.unwrap_or("default");
|
|
|
|
|
|
|
|
|
|
check_example_exists(target)?;
|
|
|
|
|
|
|
|
|
|
let local_dir = PathBuf::from(format!("./{}", name));
|
|
|
|
|
fs::create_dir(&local_dir)?;
|
|
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
copy_embedded_template(target, &local_dir)?;
|
|
|
|
|
initialize_project_with_template(&local_dir, &local_dir, name)?;
|
2025-11-29 17:31:00 +08:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_example_exists(target: &str) -> Result<()> {
|
2026-04-26 20:48:26 +08:00
|
|
|
if !embedded_example_exists(target) {
|
2025-11-29 17:31:00 +08:00
|
|
|
return Err(crate::error::CliError::NotFound(format!(
|
2026-04-26 20:48:26 +08:00
|
|
|
"example target '{}' was not found in the built-in examples",
|
2025-11-29 17:31:00 +08:00
|
|
|
target
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
fn embedded_example_exists(target: &str) -> bool {
|
|
|
|
|
let target = normalize_embedded_path(target);
|
|
|
|
|
let prefix = format!("{}/", target);
|
2025-11-29 17:31:00 +08:00
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
EMBEDDED_EXAMPLE_DIRS
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|dir| *dir == target || dir.starts_with(&prefix))
|
|
|
|
|
|| EMBEDDED_EXAMPLE_FILES
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|file| file.path.starts_with(&prefix))
|
|
|
|
|
}
|
2025-11-29 17:31:00 +08:00
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
fn copy_embedded_template(target: &str, local_dir: &PathBuf) -> Result<()> {
|
|
|
|
|
let target = normalize_embedded_path(target);
|
|
|
|
|
let prefix = format!("{}/", target);
|
2025-11-29 17:31:00 +08:00
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
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('\\', "/")
|
2025-11-29 17:31:00 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-29 23:38:03 +08:00
|
|
|
fn initialize_project_with_template(
|
|
|
|
|
template_dir: &PathBuf,
|
|
|
|
|
local_dir: &PathBuf,
|
|
|
|
|
name: &str,
|
|
|
|
|
) -> Result<()> {
|
2026-04-26 20:48:26 +08:00
|
|
|
let lower_name = lower_first_char(name)?;
|
2025-11-29 17:31:00 +08:00
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
println!("Project name: {}", name);
|
|
|
|
|
println!("Lower project name: {}", lower_name);
|
2025-11-29 17:31:00 +08:00
|
|
|
|
|
|
|
|
let project_info = generate_project_info(name, &lower_name);
|
|
|
|
|
|
2025-11-29 23:38:03 +08:00
|
|
|
let mut engine = TemplateEngine::load(template_dir)?;
|
2025-11-29 17:31:00 +08:00
|
|
|
|
2025-11-29 23:38:03 +08:00
|
|
|
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)?;
|
2026-04-26 20:48:26 +08:00
|
|
|
remove_template_config(local_dir)?;
|
2025-11-29 17:31:00 +08:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 20:48:26 +08:00
|
|
|
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<String> {
|
|
|
|
|
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()))
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 17:31:00 +08:00
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|