diff options
| author | 魏曹先生 <1992414357@qq.com> | 2025-09-26 14:18:53 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2025-09-26 14:18:53 +0800 |
| commit | 81c9f47f5d9517ab273a34aeea4b6e40f45aac36 (patch) | |
| tree | 90e1d033b28fb09d2b9c76b4658b7559acf35c2d /crates/vcs/src/data/sheet.rs | |
| parent | f5e2a00d6701729eb33da5962069c4432db426c8 (diff) | |
refactor: Update sheet input handling and fix tests
- Modify Sheet::add_input to accept InputPackage instead of separate parameters
- Use output_mappings method to generate InputPackage in tests
- Update test assertions to match new path transformation logic
- Fix mapping count assertions after adding multiple mappings
- Clean up string_proc module structure
Diffstat (limited to 'crates/vcs/src/data/sheet.rs')
| -rw-r--r-- | crates/vcs/src/data/sheet.rs | 116 |
1 files changed, 106 insertions, 10 deletions
diff --git a/crates/vcs/src/data/sheet.rs b/crates/vcs/src/data/sheet.rs index edf307a..a6765c0 100644 --- a/crates/vcs/src/data/sheet.rs +++ b/crates/vcs/src/data/sheet.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, path::PathBuf}; use cfg_file::{ConfigFile, config::ConfigFile}; use serde::{Deserialize, Serialize}; +use string_proc::simple_processer::sanitize_file_path; use crate::{ constants::SERVER_FILE_SHEET, @@ -16,14 +17,24 @@ pub type SheetPathBuf = PathBuf; pub type InputName = String; pub type InputRelativePathBuf = PathBuf; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq)] pub struct InputPackage { /// Name of the input package pub name: InputName, + + /// The sheet from which this input package was created + pub from: SheetName, + /// Files in this input package with their relative paths and virtual file IDs pub files: Vec<(InputRelativePathBuf, VirtualFileId)>, } +impl PartialEq for InputPackage { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + const SHEET_NAME: &str = "{sheet-name}"; pub struct Sheet<'a> { @@ -66,15 +77,15 @@ impl<'a> Sheet<'a> { } /// Add an input package to the sheet - pub fn add_input( - &mut self, - input_name: InputName, - files: Vec<(InputRelativePathBuf, VirtualFileId)>, - ) { - self.data.inputs.push(InputPackage { - name: input_name, - files, - }); + pub fn add_input(&mut self, input_package: InputPackage) -> Result<(), std::io::Error> { + if self.data.inputs.iter().any(|input| input == &input_package) { + return Err(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + format!("Input package '{}' already exists", input_package.name), + )); + } + self.data.inputs.push(input_package); + Ok(()) } /// Remove an input package from the sheet @@ -116,4 +127,89 @@ impl<'a> Sheet<'a> { .vault_path() .join(SERVER_FILE_SHEET.replace(SHEET_NAME, name.as_ref())) } + + /// Export files from the current sheet as an InputPackage for importing into other sheets + /// + /// This is the recommended way to create InputPackages. It takes a list of sheet paths + /// and generates an InputPackage with optimized relative paths by removing the longest + /// common prefix from all provided paths, then placing the files under a directory + /// named with the output_name. + /// + /// # Example + /// Given paths: + /// - `MyProject/Art/Character/Model/final.fbx` + /// - `MyProject/Art/Character/Texture/final.png` + /// - `MyProject/Art/Character/README.md` + /// + /// With output_name = "MyExport", the resulting package will contain: + /// - `MyExport/Model/final.fbx` + /// - `MyExport/Texture/final.png` + /// - `MyExport/README.md` + /// + /// # Arguments + /// * `output_name` - Name of the output package (will be used as the root directory) + /// * `paths` - List of sheet paths to include in the package + /// + /// # Returns + /// Returns an InputPackage containing the exported files with optimized paths, + /// or an error if paths are empty or files are not found in the sheet mapping + pub fn output_mappings( + &self, + output_name: InputName, + paths: &[SheetPathBuf], + ) -> Result<InputPackage, std::io::Error> { + let output_name = sanitize_file_path(output_name); + + // Return error for empty paths since there's no need to generate an empty package + if paths.is_empty() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Cannot generate output package with empty paths", + )); + } + + // Find the longest common prefix among all paths + let common_prefix = paths.iter().skip(1).fold(paths[0].clone(), |prefix, path| { + Self::common_path_prefix(prefix, path) + }); + + // Create output files with optimized relative paths + let files = paths + .iter() + .map(|path| { + let relative_path = path.strip_prefix(&common_prefix).unwrap_or(path); + let output_path = PathBuf::from(&output_name).join(relative_path); + + self.data + .mapping + .get(path) + .map(|vfid| (output_path, vfid.clone())) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("File not found: {:?}", path), + ) + }) + }) + .collect::<Result<Vec<_>, _>>()?; + + Ok(InputPackage { + name: output_name, + from: self.name.clone(), + files, + }) + } + + /// Helper function to find common path prefix between two paths + fn common_path_prefix(path1: impl Into<PathBuf>, path2: impl Into<PathBuf>) -> PathBuf { + let path1 = path1.into(); + let path2 = path2.into(); + + path1 + .components() + .zip(path2.components()) + .take_while(|(a, b)| a == b) + .map(|(comp, _)| comp) + .collect() + } } |
