From de2b804aad5ee54ca1654e83b251b1f9454b12ab Mon Sep 17 00:00:00 2001 From: Blank038 Date: Thu, 14 May 2026 22:16:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(init):=20=E6=94=AF=E6=8C=81=E8=A1=A5?= =?UTF-8?q?=E9=BD=90=E5=86=85=E7=BD=AE=E6=A8=A1=E6=9D=BF=E7=A9=BA=E7=9B=AE?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 init 子命令,根据项目中的 world_*_packs.json 解析实际包目录并创建标准空目录。 改用 .empty-dirs 维护内置模板空目录清单,删除会污染用户项目和网易 Bedrock 加载流程的 .gitkeep 占位文件。 --- build.rs | 86 +++++++++++++++++-- examples/default/.empty-dirs | 37 ++++++++ .../default/behavior_pack/BoxData/.gitkeep | 0 .../behavior_pack/Galaxy/Macro/.gitkeep | 0 .../behavior_pack/Galaxy/Template/.gitkeep | 0 examples/default/behavior_pack/Parts/.gitkeep | 0 .../default/behavior_pack/Presets/.gitkeep | 0 .../default/behavior_pack/entities/.gitkeep | 0 examples/default/behavior_pack/items/.gitkeep | 0 .../behavior_pack/loot_tables/.gitkeep | 0 .../netease_feature_rules/.gitkeep | 0 .../behavior_pack/netease_features/.gitkeep | 0 .../behavior_pack/netease_items_beh/.gitkeep | 0 .../default/behavior_pack/recipes/.gitkeep | 0 .../behavior_pack/spawn_rules/.gitkeep | 0 .../behavior_pack/storyline/level/.gitkeep | 0 .../default/behavior_pack/structures/.gitkeep | 0 .../default/behavior_pack/trading/.gitkeep | 0 .../animation_controllers/.gitkeep | 0 .../default/resource_pack/animations/.gitkeep | 0 .../resource_pack/attachables/.gitkeep | 0 .../default/resource_pack/effects/.gitkeep | 0 .../default/resource_pack/entity/.gitkeep | 0 examples/default/resource_pack/font/.gitkeep | 0 .../default/resource_pack/materials/.gitkeep | 0 .../resource_pack/models/animation/.gitkeep | 0 .../models/editor_materials/.gitkeep | 0 .../resource_pack/models/effect/.gitkeep | 0 .../resource_pack/models/geometry/.gitkeep | 0 .../resource_pack/models/mesh/.gitkeep | 0 .../models/netease_block/.gitkeep | 0 .../resource_pack/render_controllers/.gitkeep | 0 .../resource_pack/shaders/glsl/.gitkeep | 0 .../default/resource_pack/sounds/.gitkeep | 0 .../resource_pack/textures/entity/.gitkeep | 0 .../resource_pack/textures/models/.gitkeep | 0 .../resource_pack/textures/particle/.gitkeep | 0 .../resource_pack/textures/ui/.gitkeep | 0 examples/default/resource_pack/ui/.gitkeep | 0 src/commands/init.rs | 67 +++++++++++++++ src/commands/mod.rs | 13 +++ src/main.rs | 1 + 42 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 examples/default/.empty-dirs delete mode 100644 examples/default/behavior_pack/BoxData/.gitkeep delete mode 100644 examples/default/behavior_pack/Galaxy/Macro/.gitkeep delete mode 100644 examples/default/behavior_pack/Galaxy/Template/.gitkeep delete mode 100644 examples/default/behavior_pack/Parts/.gitkeep delete mode 100644 examples/default/behavior_pack/Presets/.gitkeep delete mode 100644 examples/default/behavior_pack/entities/.gitkeep delete mode 100644 examples/default/behavior_pack/items/.gitkeep delete mode 100644 examples/default/behavior_pack/loot_tables/.gitkeep delete mode 100644 examples/default/behavior_pack/netease_feature_rules/.gitkeep delete mode 100644 examples/default/behavior_pack/netease_features/.gitkeep delete mode 100644 examples/default/behavior_pack/netease_items_beh/.gitkeep delete mode 100644 examples/default/behavior_pack/recipes/.gitkeep delete mode 100644 examples/default/behavior_pack/spawn_rules/.gitkeep delete mode 100644 examples/default/behavior_pack/storyline/level/.gitkeep delete mode 100644 examples/default/behavior_pack/structures/.gitkeep delete mode 100644 examples/default/behavior_pack/trading/.gitkeep delete mode 100644 examples/default/resource_pack/animation_controllers/.gitkeep delete mode 100644 examples/default/resource_pack/animations/.gitkeep delete mode 100644 examples/default/resource_pack/attachables/.gitkeep delete mode 100644 examples/default/resource_pack/effects/.gitkeep delete mode 100644 examples/default/resource_pack/entity/.gitkeep delete mode 100644 examples/default/resource_pack/font/.gitkeep delete mode 100644 examples/default/resource_pack/materials/.gitkeep delete mode 100644 examples/default/resource_pack/models/animation/.gitkeep delete mode 100644 examples/default/resource_pack/models/editor_materials/.gitkeep delete mode 100644 examples/default/resource_pack/models/effect/.gitkeep delete mode 100644 examples/default/resource_pack/models/geometry/.gitkeep delete mode 100644 examples/default/resource_pack/models/mesh/.gitkeep delete mode 100644 examples/default/resource_pack/models/netease_block/.gitkeep delete mode 100644 examples/default/resource_pack/render_controllers/.gitkeep delete mode 100644 examples/default/resource_pack/shaders/glsl/.gitkeep delete mode 100644 examples/default/resource_pack/sounds/.gitkeep delete mode 100644 examples/default/resource_pack/textures/entity/.gitkeep delete mode 100644 examples/default/resource_pack/textures/models/.gitkeep delete mode 100644 examples/default/resource_pack/textures/particle/.gitkeep delete mode 100644 examples/default/resource_pack/textures/ui/.gitkeep delete mode 100644 examples/default/resource_pack/ui/.gitkeep create mode 100644 src/commands/init.rs diff --git a/build.rs b/build.rs index 3b08ba9..8d352ec 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,11 @@ use std::{ + collections::BTreeMap, env, fs, io, path::{Path, PathBuf}, }; +const EMPTY_DIRS_FILE: &str = ".empty-dirs"; + fn main() { println!("cargo:rerun-if-changed=examples"); @@ -12,25 +15,37 @@ fn main() { let mut dirs = Vec::new(); let mut files = Vec::new(); + let mut empty_dirs_by_example = BTreeMap::new(); if examples_root.is_dir() { - collect_examples(&examples_root, &examples_root, &mut dirs, &mut files) - .expect("failed to collect example templates"); + collect_examples( + &examples_root, + &examples_root, + &mut dirs, + &mut files, + &mut empty_dirs_by_example, + ) + .expect("failed to collect example templates"); } dirs.sort(); + dirs.dedup(); files.sort_by(|a, b| a.0.cmp(&b.0)); + for empty_dirs in empty_dirs_by_example.values_mut() { + empty_dirs.sort(); + empty_dirs.dedup(); + } let mut generated = String::new(); generated.push_str( - "struct EmbeddedFile {\n path: &'static str,\n contents: &'static [u8],\n}\n\n", + "#[allow(dead_code)]\nstruct EmbeddedFile {\n path: &'static str,\n contents: &'static [u8],\n}\n\n", ); - generated.push_str("static EMBEDDED_EXAMPLE_DIRS: &[&str] = &[\n"); + generated.push_str("#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_DIRS: &[&str] = &[\n"); for dir in &dirs { generated.push_str(&format!(" {:?},\n", dir)); } generated.push_str("];\n\n"); - generated.push_str("static EMBEDDED_EXAMPLE_FILES: &[EmbeddedFile] = &[\n"); + generated.push_str("#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_FILES: &[EmbeddedFile] = &[\n"); for (relative_path, absolute_path) in &files { generated.push_str(&format!( " EmbeddedFile {{ path: {:?}, contents: include_bytes!(r#\"{}\"#) }},\n", @@ -38,6 +53,23 @@ fn main() { absolute_path.display() )); } + generated.push_str("];\n\n"); + generated.push_str( + "#[allow(dead_code)]\nstruct EmbeddedExampleEmptyDirs {\n example: &'static str,\n dirs: &'static [&'static str],\n}\n\n", + ); + generated.push_str( + "#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_EMPTY_DIRS: &[EmbeddedExampleEmptyDirs] = &[\n", + ); + for (example, empty_dirs) in &empty_dirs_by_example { + generated.push_str(&format!( + " EmbeddedExampleEmptyDirs {{ example: {:?}, dirs: &[\n", + example + )); + for dir in empty_dirs { + generated.push_str(&format!(" {:?},\n", dir)); + } + generated.push_str(" ] },\n"); + } generated.push_str("];\n"); fs::write(out_file, generated).expect("failed to write embedded example list"); @@ -48,6 +80,7 @@ fn collect_examples( dir: &Path, dirs: &mut Vec, files: &mut Vec<(String, PathBuf)>, + empty_dirs_by_example: &mut BTreeMap>, ) -> io::Result<()> { for entry in fs::read_dir(dir)? { let entry = entry?; @@ -60,11 +93,50 @@ fn collect_examples( if path.is_dir() { dirs.push(relative_path); - collect_examples(root, &path, dirs, files)?; + collect_examples(root, &path, dirs, files, empty_dirs_by_example)?; } else if path.is_file() { - files.push((relative_path, path.canonicalize()?)); + if path.file_name().and_then(|name| name.to_str()) == Some(EMPTY_DIRS_FILE) { + collect_empty_dirs(&relative_path, &path, dirs, empty_dirs_by_example)?; + } else { + files.push((relative_path, path.canonicalize()?)); + } } } Ok(()) } + +fn collect_empty_dirs( + relative_path: &str, + path: &Path, + dirs: &mut Vec, + empty_dirs_by_example: &mut BTreeMap>, +) -> io::Result<()> { + let (example, file_name) = relative_path.split_once('/').ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("{} must be inside an example directory", EMPTY_DIRS_FILE), + ) + })?; + if file_name != EMPTY_DIRS_FILE { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unexpected {} path: {}", EMPTY_DIRS_FILE, relative_path), + )); + } + + let contents = fs::read_to_string(path)?; + let empty_dirs = empty_dirs_by_example + .entry(example.to_string()) + .or_insert_with(Vec::new); + for line in contents.lines() { + let dir = line.trim(); + if dir.is_empty() || dir.starts_with('#') { + continue; + } + dirs.push(format!("{}/{}", example, dir)); + empty_dirs.push(dir.to_string()); + } + + Ok(()) +} diff --git a/examples/default/.empty-dirs b/examples/default/.empty-dirs new file mode 100644 index 0000000..b28ee75 --- /dev/null +++ b/examples/default/.empty-dirs @@ -0,0 +1,37 @@ +behavior_pack/BoxData +behavior_pack/entities +behavior_pack/items +behavior_pack/loot_tables +behavior_pack/netease_feature_rules +behavior_pack/netease_features +behavior_pack/netease_items_beh +behavior_pack/Parts +behavior_pack/Presets +behavior_pack/recipes +behavior_pack/spawn_rules +behavior_pack/structures +behavior_pack/trading +behavior_pack/Galaxy/Macro +behavior_pack/Galaxy/Template +behavior_pack/storyline/level +resource_pack/animation_controllers +resource_pack/animations +resource_pack/attachables +resource_pack/effects +resource_pack/entity +resource_pack/font +resource_pack/materials +resource_pack/models/animation +resource_pack/models/editor_materials +resource_pack/models/effect +resource_pack/models/geometry +resource_pack/models/mesh +resource_pack/models/netease_block +resource_pack/render_controllers +resource_pack/shaders/glsl +resource_pack/sounds +resource_pack/textures/entity +resource_pack/textures/models +resource_pack/textures/particle +resource_pack/textures/ui +resource_pack/ui diff --git a/examples/default/behavior_pack/BoxData/.gitkeep b/examples/default/behavior_pack/BoxData/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/Galaxy/Macro/.gitkeep b/examples/default/behavior_pack/Galaxy/Macro/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/Galaxy/Template/.gitkeep b/examples/default/behavior_pack/Galaxy/Template/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/Parts/.gitkeep b/examples/default/behavior_pack/Parts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/Presets/.gitkeep b/examples/default/behavior_pack/Presets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/entities/.gitkeep b/examples/default/behavior_pack/entities/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/items/.gitkeep b/examples/default/behavior_pack/items/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/loot_tables/.gitkeep b/examples/default/behavior_pack/loot_tables/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/netease_feature_rules/.gitkeep b/examples/default/behavior_pack/netease_feature_rules/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/netease_features/.gitkeep b/examples/default/behavior_pack/netease_features/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/netease_items_beh/.gitkeep b/examples/default/behavior_pack/netease_items_beh/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/recipes/.gitkeep b/examples/default/behavior_pack/recipes/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/spawn_rules/.gitkeep b/examples/default/behavior_pack/spawn_rules/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/storyline/level/.gitkeep b/examples/default/behavior_pack/storyline/level/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/structures/.gitkeep b/examples/default/behavior_pack/structures/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/behavior_pack/trading/.gitkeep b/examples/default/behavior_pack/trading/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/animation_controllers/.gitkeep b/examples/default/resource_pack/animation_controllers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/animations/.gitkeep b/examples/default/resource_pack/animations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/attachables/.gitkeep b/examples/default/resource_pack/attachables/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/effects/.gitkeep b/examples/default/resource_pack/effects/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/entity/.gitkeep b/examples/default/resource_pack/entity/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/font/.gitkeep b/examples/default/resource_pack/font/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/materials/.gitkeep b/examples/default/resource_pack/materials/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/models/animation/.gitkeep b/examples/default/resource_pack/models/animation/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/models/editor_materials/.gitkeep b/examples/default/resource_pack/models/editor_materials/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/models/effect/.gitkeep b/examples/default/resource_pack/models/effect/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/models/geometry/.gitkeep b/examples/default/resource_pack/models/geometry/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/models/mesh/.gitkeep b/examples/default/resource_pack/models/mesh/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/models/netease_block/.gitkeep b/examples/default/resource_pack/models/netease_block/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/render_controllers/.gitkeep b/examples/default/resource_pack/render_controllers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/shaders/glsl/.gitkeep b/examples/default/resource_pack/shaders/glsl/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/sounds/.gitkeep b/examples/default/resource_pack/sounds/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/textures/entity/.gitkeep b/examples/default/resource_pack/textures/entity/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/textures/models/.gitkeep b/examples/default/resource_pack/textures/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/textures/particle/.gitkeep b/examples/default/resource_pack/textures/particle/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/textures/ui/.gitkeep b/examples/default/resource_pack/textures/ui/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/resource_pack/ui/.gitkeep b/examples/default/resource_pack/ui/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/init.rs b/src/commands/init.rs new file mode 100644 index 0000000..b269284 --- /dev/null +++ b/src/commands/init.rs @@ -0,0 +1,67 @@ +use std::fs; + +use crate::commands::InitArgs; +use crate::entity; +use crate::error::{CliError, Result}; +use crate::utils::file; + +include!(concat!(env!("OUT_DIR"), "/embedded_examples.rs")); + +pub fn execute(args: &InitArgs) { + if let Err(e) = run_init(args) { + eprintln!("❌ 项目初始化失败: {}", e); + return; + } + println!("🍀 项目初始化完成"); +} + +fn run_init(args: &InitArgs) -> Result<()> { + let project_dir = file::find_project_dir(&args.path)?; + let target = args.target.as_deref().unwrap_or("default"); + let empty_dirs = lookup_empty_dirs(target)?; + let release_info = entity::get_current_release_info(&project_dir)?; + + let behavior_pack = format!("behavior_pack_{}", release_info.behavior_identifier); + let resource_pack = format!("resource_pack_{}", release_info.resource_identifier); + + let mut created = 0usize; + let mut skipped = 0usize; + + for rel in empty_dirs { + let rewritten = rewrite_pack_path(rel, &behavior_pack, &resource_pack); + let abs = project_dir.join(&rewritten); + if abs.is_dir() { + skipped += 1; + continue; + } + fs::create_dir_all(&abs).map_err(|e| file::io_error("创建目录", &abs, e))?; + println!("📁 创建目录: {}", rewritten); + created += 1; + } + + println!("✅ 新建 {} 个目录, 跳过 {} 个已存在目录", created, skipped); + Ok(()) +} + +fn lookup_empty_dirs(target: &str) -> Result<&'static [&'static str]> { + EMBEDDED_EXAMPLE_EMPTY_DIRS + .iter() + .find(|e| e.example == target) + .map(|e| e.dirs) + .ok_or_else(|| { + CliError::NotFound(format!( + "example target '{}' was not found in the built-in examples", + target + )) + }) +} + +fn rewrite_pack_path(rel: &str, behavior: &str, resource: &str) -> String { + if let Some(rest) = rel.strip_prefix("behavior_pack/") { + format!("{}/{}", behavior, rest) + } else if let Some(rest) = rel.strip_prefix("resource_pack/") { + format!("{}/{}", resource, rest) + } else { + rel.to_string() + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0f7258d..9e24f87 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,6 +2,7 @@ use clap::{Args, Parser, Subcommand, arg}; pub mod components; pub mod create; +pub mod init; pub mod release; #[derive(Parser)] @@ -24,6 +25,8 @@ pub enum Commands { Release(ReleaseArgs), /// Create a new mod project Create(CreateArgs), + /// Initialize standard empty directories for an existing project + Init(InitArgs), /// Create a new component Components(ComponentsArgs), } @@ -52,6 +55,16 @@ pub struct CreateArgs { pub target: Option, } +#[derive(Args)] +pub struct InitArgs { + /// The path of the project (default: current directory) + #[arg(short, long)] + pub path: Option, + /// Example target whose layout to apply + #[arg(short, long)] + pub target: Option, +} + #[derive(Args)] pub struct ComponentsArgs { /// The path of the project diff --git a/src/main.rs b/src/main.rs index e47678d..015d73a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ fn main() { match &cli.command { Commands::Release(args) => commands::release::execute(args), Commands::Create(args) => commands::create::execute(args), + Commands::Init(args) => commands::init::execute(args), Commands::Components(args) => commands::components::execute(args), } }