summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2025-09-26 14:18:53 +0800
committer魏曹先生 <1992414357@qq.com>2025-09-26 14:18:53 +0800
commit81c9f47f5d9517ab273a34aeea4b6e40f45aac36 (patch)
tree90e1d033b28fb09d2b9c76b4658b7559acf35c2d /crates
parentf5e2a00d6701729eb33da5962069c4432db426c8 (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')
-rw-r--r--crates/utils/string_proc/src/format_processer.rs (renamed from crates/utils/string_proc/src/string_processer.rs)16
-rw-r--r--crates/utils/string_proc/src/lib.rs9
-rw-r--r--crates/utils/string_proc/src/macros.rs32
-rw-r--r--crates/utils/string_proc/src/simple_processer.rs15
-rw-r--r--crates/vcs/src/data/sheet.rs116
-rw-r--r--crates/vcs/src/data/vault.rs4
-rw-r--r--crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs53
7 files changed, 186 insertions, 59 deletions
diff --git a/crates/utils/string_proc/src/string_processer.rs b/crates/utils/string_proc/src/format_processer.rs
index 8b51c12..8d0a770 100644
--- a/crates/utils/string_proc/src/string_processer.rs
+++ b/crates/utils/string_proc/src/format_processer.rs
@@ -1,8 +1,8 @@
-pub struct StringProcesser {
+pub struct FormatProcesser {
content: Vec<String>,
}
-impl From<String> for StringProcesser {
+impl From<String> for FormatProcesser {
fn from(value: String) -> Self {
Self {
content: Self::process_string(value),
@@ -10,7 +10,7 @@ impl From<String> for StringProcesser {
}
}
-impl From<&str> for StringProcesser {
+impl From<&str> for FormatProcesser {
fn from(value: &str) -> Self {
Self {
content: Self::process_string(value.to_string()),
@@ -18,7 +18,7 @@ impl From<&str> for StringProcesser {
}
}
-impl StringProcesser {
+impl FormatProcesser {
/// Process the string into an intermediate format
fn process_string(input: String) -> Vec<String> {
let mut result = String::new();
@@ -46,9 +46,11 @@ impl StringProcesser {
while let Some(c) = chars.next() {
processed.push(c);
if let Some(&next) = chars.peek()
- && c.is_lowercase() && next.is_uppercase() {
- processed.push(' ');
- }
+ && c.is_lowercase()
+ && next.is_uppercase()
+ {
+ processed.push(' ');
+ }
}
processed
diff --git a/crates/utils/string_proc/src/lib.rs b/crates/utils/string_proc/src/lib.rs
index 1f24028..e5559b9 100644
--- a/crates/utils/string_proc/src/lib.rs
+++ b/crates/utils/string_proc/src/lib.rs
@@ -1,9 +1,10 @@
+pub mod format_processer;
pub mod macros;
-pub mod string_processer;
+pub mod simple_processer;
#[cfg(test)]
mod tests {
- use crate::string_processer::StringProcesser;
+ use crate::format_processer::FormatProcesser;
#[test]
fn test_processer() {
@@ -22,7 +23,7 @@ mod tests {
];
for (input, expected) in test_cases {
- let processor = StringProcesser::from(input);
+ let processor = FormatProcesser::from(input);
assert_eq!(
processor.to_camel_case(),
expected,
@@ -34,7 +35,7 @@ mod tests {
#[test]
fn test_conversions() {
- let processor = StringProcesser::from("brewCoffee");
+ let processor = FormatProcesser::from("brewCoffee");
assert_eq!(processor.to_upper_case(), "BREW COFFEE");
assert_eq!(processor.to_lower_case(), "brew coffee");
diff --git a/crates/utils/string_proc/src/macros.rs b/crates/utils/string_proc/src/macros.rs
index 9d85338..135268e 100644
--- a/crates/utils/string_proc/src/macros.rs
+++ b/crates/utils/string_proc/src/macros.rs
@@ -1,63 +1,63 @@
#[macro_export]
macro_rules! camel_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_camel_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_camel_case()
}};
}
#[macro_export]
macro_rules! upper_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_upper_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_upper_case()
}};
}
#[macro_export]
macro_rules! lower_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_lower_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_lower_case()
}};
}
#[macro_export]
macro_rules! title_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_title_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_title_case()
}};
}
#[macro_export]
macro_rules! dot_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_dot_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_dot_case()
}};
}
#[macro_export]
macro_rules! snake_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_snake_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_snake_case()
}};
}
#[macro_export]
macro_rules! kebab_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_kebab_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_kebab_case()
}};
}
#[macro_export]
macro_rules! pascal_case {
($input:expr) => {{
- use string_proc::string_processer::StringProcesser;
- StringProcesser::from($input).to_pascal_case()
+ use string_proc::format_processer::FormatProcesser;
+ FormatProcesser::from($input).to_pascal_case()
}};
}
diff --git a/crates/utils/string_proc/src/simple_processer.rs b/crates/utils/string_proc/src/simple_processer.rs
new file mode 100644
index 0000000..2de5dfc
--- /dev/null
+++ b/crates/utils/string_proc/src/simple_processer.rs
@@ -0,0 +1,15 @@
+/// Sanitizes a file path by replacing special characters with underscores.
+///
+/// This function takes a file path as input and returns a sanitized version
+/// where characters that are not allowed in file paths (such as path separators
+/// and other reserved characters) are replaced with underscores.
+pub fn sanitize_file_path<P: AsRef<str>>(path: P) -> String {
+ let path_str = path.as_ref();
+ path_str
+ .chars()
+ .map(|c| match c {
+ '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
+ _ => c,
+ })
+ .collect()
+}
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()
+ }
}
diff --git a/crates/vcs/src/data/vault.rs b/crates/vcs/src/data/vault.rs
index 5b34c6f..5d17a81 100644
--- a/crates/vcs/src/data/vault.rs
+++ b/crates/vcs/src/data/vault.rs
@@ -72,9 +72,7 @@ impl Vault {
create_dir_all(vault_path.join(SERVER_PATH_VF_ROOT))?;
let Some(vault) = Vault::init(config, &vault_path) else {
- return Err(std::io::Error::other(
- "Failed to initialize vault",
- ));
+ return Err(std::io::Error::other("Failed to initialize vault"));
};
// 6. Create host member
diff --git a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs b/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
index 1c7182b..8abcc4d 100644
--- a/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
+++ b/crates/vcs/vcs_test/src/test_sheet_creation_management_and_persistence.rs
@@ -48,20 +48,23 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
// Test 2: Add input packages to the sheet
let input_name = "source_files".to_string();
- let files = vec![
- (
- InputRelativePathBuf::from("src/main.rs"),
- VirtualFileId::new(),
- ),
- (
- InputRelativePathBuf::from("src/lib.rs"),
- VirtualFileId::new(),
- ),
- ];
- // Need to get a mutable reference to the sheet
+ // First add mapping entries that will be used to generate the input package
let mut sheet = vault.sheet(&sheet_name).await?;
- sheet.add_input(input_name.clone(), files.clone());
+
+ // Add mapping entries for the files
+ let main_rs_path = vcs::data::sheet::SheetPathBuf::from("src/main.rs");
+ let lib_rs_path = vcs::data::sheet::SheetPathBuf::from("src/lib.rs");
+ let main_rs_id = VirtualFileId::new();
+ let lib_rs_id = VirtualFileId::new();
+
+ sheet.add_mapping(main_rs_path.clone(), main_rs_id.clone());
+ sheet.add_mapping(lib_rs_path.clone(), lib_rs_id.clone());
+
+ // Use output_mappings to generate the InputPackage
+ let paths = vec![main_rs_path, lib_rs_path];
+ let input_package = sheet.output_mappings(input_name.clone(), &paths)?;
+ sheet.add_input(input_package)?;
// Verify input was added
assert_eq!(sheet.inputs().len(), 1);
@@ -70,11 +73,11 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
assert_eq!(added_input.files.len(), 2);
assert_eq!(
added_input.files[0].0,
- InputRelativePathBuf::from("src/main.rs")
+ InputRelativePathBuf::from("source_files/main.rs")
);
assert_eq!(
added_input.files[1].0,
- InputRelativePathBuf::from("src/lib.rs")
+ InputRelativePathBuf::from("source_files/lib.rs")
);
// Test 3: Add mapping entries
@@ -83,7 +86,7 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
sheet.add_mapping(mapping_path.clone(), virtual_file_id.clone());
// Verify mapping was added
- assert_eq!(sheet.mapping().len(), 1);
+ assert_eq!(sheet.mapping().len(), 3);
assert_eq!(sheet.mapping().get(&mapping_path), Some(&virtual_file_id));
// Test 4: Persist sheet to disk
@@ -93,7 +96,7 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
let reloaded_sheet = vault.sheet(&sheet_name).await?;
assert_eq!(reloaded_sheet.holder(), &member_id);
assert_eq!(reloaded_sheet.inputs().len(), 1);
- assert_eq!(reloaded_sheet.mapping().len(), 1);
+ assert_eq!(reloaded_sheet.mapping().len(), 3);
// Test 5: Remove input package
let mut sheet_for_removal = vault.sheet(&sheet_name).await?;
@@ -107,7 +110,7 @@ async fn test_sheet_creation_management_and_persistence() -> Result<(), std::io:
// Test 6: Remove mapping entry
let removed_virtual_file_id = sheet_for_removal.remove_mapping(&mapping_path);
assert_eq!(removed_virtual_file_id, Some(virtual_file_id));
- assert_eq!(sheet_for_removal.mapping().len(), 0);
+ assert_eq!(sheet_for_removal.mapping().len(), 2);
// Test 7: List all sheets in vault
let sheet_names = vault.sheet_names()?;
@@ -244,7 +247,7 @@ async fn test_sheet_data_serialization() -> Result<(), std::io::Error> {
// Add some inputs
let input_name = "source_files".to_string();
- let files = vec![
+ let _files = vec![
(
InputRelativePathBuf::from("src/main.rs"),
VirtualFileId::new(),
@@ -254,7 +257,19 @@ async fn test_sheet_data_serialization() -> Result<(), std::io::Error> {
VirtualFileId::new(),
),
];
- sheet.add_input(input_name, files);
+ // First add mapping entries
+ let main_rs_path = vcs::data::sheet::SheetPathBuf::from("src/main.rs");
+ let lib_rs_path = vcs::data::sheet::SheetPathBuf::from("src/lib.rs");
+ let main_rs_id = VirtualFileId::new();
+ let lib_rs_id = VirtualFileId::new();
+
+ sheet.add_mapping(main_rs_path.clone(), main_rs_id.clone());
+ sheet.add_mapping(lib_rs_path.clone(), lib_rs_id.clone());
+
+ // Use output_mappings to generate the InputPackage
+ let paths = vec![main_rs_path, lib_rs_path];
+ let input_package = sheet.output_mappings(input_name.clone(), &paths)?;
+ sheet.add_input(input_package)?;
// Add some mappings
sheet.add_mapping(