From eec323baf28f1a588f835aa773b77e019f91446d Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 12 Feb 2026 04:42:10 +0800 Subject: Add sheet system with mapping macros and modules --- systems/sheet/src/lib.rs | 6 + systems/sheet/src/mapping.rs | 422 +++++++++++++++++++++++++++++++++++ systems/sheet/src/mapping_pattern.rs | 173 ++++++++++++++ systems/sheet/src/sheet.rs | 1 + 4 files changed, 602 insertions(+) create mode 100644 systems/sheet/src/mapping.rs create mode 100644 systems/sheet/src/mapping_pattern.rs create mode 100644 systems/sheet/src/sheet.rs (limited to 'systems/sheet/src') diff --git a/systems/sheet/src/lib.rs b/systems/sheet/src/lib.rs index 8b13789..94e84c5 100644 --- a/systems/sheet/src/lib.rs +++ b/systems/sheet/src/lib.rs @@ -1 +1,7 @@ +pub mod mapping; +pub mod mapping_pattern; +pub mod sheet; +pub mod macros { + pub use sheet_system_macros::*; +} diff --git a/systems/sheet/src/mapping.rs b/systems/sheet/src/mapping.rs new file mode 100644 index 0000000..b31315d --- /dev/null +++ b/systems/sheet/src/mapping.rs @@ -0,0 +1,422 @@ +use string_proc::{ + format_path::{PathFormatConfig, format_path_str, format_path_str_with_config}, + snake_case, +}; + +/// Local mapping +/// It is stored inside a Sheet and will be exposed externally as Mapping or MappingBuf +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LocalMapping { + /// The value of the local mapping + val: Vec, + + /// The ID of the local mapping + id: String, + + /// The version of the local mapping + ver: String, + + /// The version direction of the local mapping + forward: LocalMappingForward, +} + +/// The forward direction of the current Mapping +/// It indicates the expected asset update method for the current Mapping +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum LocalMappingForward { + /// Expect the current index version to be the latest + Latest, + + /// Expect the current index version to point to a specific Ref + /// Note: When the Ref points to a Sheet that does not have this index, + /// its Forward will become `Version(current_version)` + Ref { sheet_name: String }, + + /// Expect the current index version to point to a specific version + Version { version_name: String }, +} + +/// Mapping +/// It stores basic mapping information and only participates in comparison and parsing +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Mapping<'a> { + sheet_name: &'a str, + val: &'a str, + id: &'a str, + ver: &'a str, +} + +/// MappingBuf +/// It stores complete mapping information and participates in complex mapping editing operations like storage and modification +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct MappingBuf { + sheet_name: String, + val: Vec, + val_joined: String, + id: String, + ver: String, +} + +// Implement creation and mutual conversion for MappingBuf, LocalMapping and Mapping + +impl LocalMapping { + /// Create a new LocalMapping + pub fn new( + val: Vec, + id: impl Into, + ver: impl Into, + forward: LocalMappingForward, + ) -> Self { + Self { + val, + id: id.into(), + ver: ver.into(), + forward, + } + } + + /// Get the path value of LocalMapping + pub fn value(&self) -> &Vec { + &self.val + } + + /// Get the mapped index ID of LocalMapping + pub fn mapped_id(&self) -> &String { + &self.id + } + + /// Get the mapped index version of LocalMapping + pub fn mapped_version(&self) -> &String { + &self.ver + } + + /// Get the forward direction of LocalMapping + pub fn forward(&self) -> &LocalMappingForward { + &self.forward + } + + /// Clone and generate a MappingBuf from LocalMapping + pub fn to_mapping_buf_cloned(&self, sheet_name: impl Into) -> MappingBuf { + MappingBuf::new( + sheet_name.into(), + self.val.clone(), + self.id.clone(), + self.ver.clone(), + ) + } + + /// Generate a MappingBuf from LocalMapping + pub fn to_mapping_buf(self, sheet_name: impl Into) -> MappingBuf { + MappingBuf::new(sheet_name.into(), self.val, self.id, self.ver) + } +} + +impl MappingBuf { + /// Create a new MappingBuf + pub fn new( + sheet_name: impl Into, + val: Vec, + id: impl Into, + ver: impl Into, + ) -> Self { + let val_joined = val.join("/"); + Self { + sheet_name: sheet_name.into(), + val, + val_joined, + id: id.into(), + ver: ver.into(), + } + } + + /// Get the sheet name of MappingBuf + pub fn sheet_name(&self) -> &String { + &self.sheet_name + } + + /// Get the path value of MappingBuf + pub fn value(&self) -> &Vec { + &self.val + } + + /// Get the path value string of MappingBuf + pub fn value_str(&self) -> &String { + &self.val_joined + } + + /// Get the mapped index ID of MappingBuf + pub fn mapped_id(&self) -> &String { + &self.id + } + + /// Get the mapped index version of MappingBuf + pub fn mapped_version(&self) -> &String { + &self.ver + } + + /// Generate a Mapping from MappingBuf + pub fn as_mapping(&self) -> Mapping<'_> { + Mapping::new(&self.sheet_name, &self.val_joined, &self.id, &self.ver) + } + + /// Clone and generate a LocalMapping from MappingBuf + pub fn to_local_mapping_cloned(&self, forward: &LocalMappingForward) -> LocalMapping { + LocalMapping::new( + self.val.clone(), + self.id.clone(), + self.ver.clone(), + forward.clone(), + ) + } + + /// Generate a LocalMapping from MappingBuf + pub fn to_local_mapping(self, forward: LocalMappingForward) -> LocalMapping { + LocalMapping::new(self.val, self.id, self.ver, forward) + } +} + +impl<'a> Mapping<'a> { + /// Create a new Mapping + pub fn new(sheet_name: &'a str, val: &'a str, id: &'a str, ver: &'a str) -> Self { + Self { + sheet_name, + val, + id, + ver, + } + } + + /// Get the sheet name of Mapping + pub fn sheet_name(&self) -> &str { + &self.sheet_name + } + + /// Build a Vec of Mapping values from the stored address + pub fn value(&self) -> Vec { + format_path_str(self.val.to_string()) + .unwrap_or_default() + .split("/") + .map(|s| s.to_string()) + .collect() + } + + /// Get the value str of Mapping + pub fn value_str(&self) -> &str { + &self.val + } + + /// Get the mapped index ID of Mapping + pub fn mapped_id(&self) -> &str { + &self.id + } + + /// Get the mapped index version of Mapping + pub fn mapped_version(&self) -> &str { + &self.ver + } + + /// Generate a MappingBuf from Mapping + pub fn to_mapping_buf(&self) -> MappingBuf { + MappingBuf::new( + self.sheet_name.to_string(), + format_path_str(self.val) + .unwrap_or_default() + .split('/') + .into_iter() + .map(|s| s.to_string()) + .collect(), + self.id.to_string(), + self.ver.to_string(), + ) + } + + /// Generate a LocalMapping from MappingBuf + pub fn to_local_mapping(self, forward: LocalMappingForward) -> LocalMapping { + LocalMapping::new( + format_path_str(self.val) + .unwrap_or_default() + .split("/") + .into_iter() + .map(|s| s.to_string()) + .collect(), + self.id.to_string(), + self.ver.to_string(), + forward, + ) + } +} + +impl<'a> From> for MappingBuf { + fn from(mapping: Mapping<'a>) -> Self { + mapping.to_mapping_buf() + } +} + +// Implement the Display trait for Mapping, LocalMapping and MappingBuf for formatted output. +// +// The Display implementation only shows path information, not the complete structure information. +// Why? +// +// Because Display is primarily used for user-friendly presentation, not for internal program use. +// When presenting, only the snake_case converted sheet_name and the path formed by joining val are shown. + +macro_rules! fmt_mapping { + ($f:expr, $sheet_name:expr, $val:expr) => { + write!( + $f, + "{}:/{}", + snake_case!($sheet_name), + format_path_str($val).unwrap_or_default() + ) + }; +} + +impl<'a> std::fmt::Display for Mapping<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_mapping!(f, self.sheet_name, self.val) + } +} + +impl std::fmt::Display for MappingBuf { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_mapping!(f, self.sheet_name.to_string(), &self.val.join("/")) + } +} + +impl std::fmt::Display for LocalMapping { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.val.join("/")) + } +} + +// Implement editing functionality for MappingBuf and LocalMapping + +impl MappingBuf { + /// Append new nodes to the end of MappingBuf to modify the path + pub fn join(mut self, nodes: impl Into) -> Self { + let nodes = nodes.into(); + let mapping_buf_val = join_helper(nodes, self.val); + self.val_joined = mapping_buf_val.join("/"); + self.val = mapping_buf_val; + self + } + + /// Set the sheet name of the current MappingBuf + pub fn set_sheet_name(&mut self, sheet_name: impl Into) { + self.sheet_name = sheet_name.into(); + } + + /// Set the value of the current MappingBuf + pub fn set_value(&mut self, val: Vec) { + self.val = val; + self.val_joined = self.val.join("/"); + } + + /// Set the mapped index ID of the current MappingBuf + pub fn set_mapped_id(&mut self, id: impl Into) { + self.id = id.into(); + } + + /// Set the mapped index version of the current MappingBuf + pub fn set_mapped_version(&mut self, version: impl Into) { + self.ver = version.into(); + } +} + +impl LocalMapping { + /// Append new nodes to the end of MappingBuf to modify the path + pub fn join(mut self, nodes: impl Into) -> Self { + let nodes = nodes.into(); + let mapping_buf_val = join_helper(nodes, self.val); + self.val = mapping_buf_val; + self + } + + /// Set the value of the current LocalMapping + pub fn set_value(&mut self, val: Vec) { + self.val = val; + } + + /// Set the mapped index ID of the current LocalMapping + pub fn set_mapped_id(&mut self, id: impl Into) { + self.id = id.into(); + } + + /// Set the mapped index version of the current LocalMapping + pub fn set_mapped_version(&mut self, version: impl Into) { + self.ver = version.into(); + } + + /// Set the forward direction of the current LocalMapping + pub fn set_forward(&mut self, forward: &LocalMappingForward) { + self.forward = forward.clone(); + } +} + +#[inline(always)] +fn join_helper(nodes: String, mut mapping_buf_val: Vec) -> Vec { + let formatted = format_path_str_with_config( + nodes, + &PathFormatConfig { + // Do not process ".." because it is used to go up one level + resolve_parent_dirs: false, + ..Default::default() + }, + ) + .unwrap_or_default(); + let sliced_nodes = formatted.split('/'); + for node in sliced_nodes.into_iter() { + match node { + "." => continue, + ".." => { + // If the length of Mapping is greater than 1, remove the last item + if mapping_buf_val.len() > 1 { + let _ = mapping_buf_val.remove(mapping_buf_val.len() - 1); + } + } + _ => { + mapping_buf_val.push(node.to_string()); + } + } + } + + return mapping_buf_val; +} + +// Implement mutual comparison for LocalMapping, MappingBuf, and Mapping + +impl<'a> PartialEq> for LocalMapping { + fn eq(&self, other: &Mapping<'a>) -> bool { + self.val.join("/") == other.val && self.id == other.id && self.ver == other.ver + } +} + +impl<'a> PartialEq for Mapping<'a> { + fn eq(&self, other: &LocalMapping) -> bool { + other == self + } +} + +impl PartialEq for LocalMapping { + fn eq(&self, other: &MappingBuf) -> bool { + self.val == other.val && self.id == other.id && self.ver == other.ver + } +} + +impl PartialEq for MappingBuf { + fn eq(&self, other: &LocalMapping) -> bool { + other == self + } +} + +impl<'a> PartialEq for Mapping<'a> { + fn eq(&self, other: &MappingBuf) -> bool { + self.val == other.val_joined && self.id == other.id && self.ver == other.ver + } +} + +impl<'a> PartialEq> for MappingBuf { + fn eq(&self, other: &Mapping<'a>) -> bool { + other == self + } +} diff --git a/systems/sheet/src/mapping_pattern.rs b/systems/sheet/src/mapping_pattern.rs new file mode 100644 index 0000000..2b30c0d --- /dev/null +++ b/systems/sheet/src/mapping_pattern.rs @@ -0,0 +1,173 @@ +// Mapping Pattern +// 是用来匹配多个 Mapping 的语法 +// +// ~ 当前 Sheet +// +// 省略机制 +// +// 如果上下文有sheet,那就是 +// mapping/file.suffix +// +// 如果没有sheet,那就是 +// sheet:/mapping/file.suffix +// +// 可以使用路径语法 +// +// sheet:/mapping/../file.suffix +// +// 使用以下逻辑匹配文件 +// +// sheet:/. 匹配 sheet 中 / 下所有文件 +// sheet:/arts/. 匹配结果为 sheet:/arts/ 下的文件 +// sheet:/arts/ 匹配结果为 sheet:/arts/ 下的文件 +// sheet:/arts 匹配结果为 sheet:/arts 文件夹 +// +// 文件名匹配机制 +// +// *.md 匹配所有 md 文件 +// [Mm]essages 匹配 Message 或 message +// 使用\[Mm\]essage 匹配 [Mm]essage +// 使用 ** 匹配目录下所有文件(递归) +// 使用 * 匹配目录下所有文件 +// 使用 ![XX] 匹配排除以外的所有文件,例如: +// +// ![README.md]* 匹配除名叫 README.md 以外的所有当前目录下的文件 +// ![[Rr][Ee][Aa][Dd][Mm][Ee].[Mm][Dd]]** 匹配所有 不叫 README.md 的文件 +// +// ![**/temp]** 匹配所有不在temp目录的文件 +// +// MappingPattern会根据MappingContext生成MappingPatternResult +// 准确来说是 +// PatternResult::Single Or PatternResult::Multi +// PatternResult可以unwrap为single()或multi() + +use crate::mapping::MappingBuf; + +pub struct MappingPattern {} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum MappingPatternResult { + Single(MappingBuf), + Multi(Vec), +} + +impl MappingPatternResult { + pub fn new_single(mapping: MappingBuf) -> Self { + Self::Single(mapping) + } + + pub fn new_multi(mappings: Vec) -> Self { + Self::Multi(mappings) + } + + pub fn is_single(&self) -> bool { + match self { + MappingPatternResult::Single(_) => true, + MappingPatternResult::Multi(_) => false, + } + } + + pub fn is_multi(&self) -> bool { + match self { + MappingPatternResult::Single(_) => false, + MappingPatternResult::Multi(_) => true, + } + } + + pub fn single(self) -> Option { + match self { + MappingPatternResult::Single(mapping) => Some(mapping), + MappingPatternResult::Multi(_) => None, + } + } + + pub fn multi(self) -> Option> { + match self { + MappingPatternResult::Single(_) => None, + MappingPatternResult::Multi(mappings) => Some(mappings), + } + } + + pub fn ensure_multi(self) -> Vec { + match self { + MappingPatternResult::Single(mapping) => vec![mapping], + MappingPatternResult::Multi(mappings) => mappings, + } + } + + pub fn unwrap_single(self) -> MappingBuf { + match self { + MappingPatternResult::Single(mapping) => mapping, + MappingPatternResult::Multi(_) => panic!("Called `unwrap_single()` on a `Multi` value"), + } + } + + pub fn unwrap_multi(self) -> Vec { + match self { + MappingPatternResult::Single(_) => { + panic!("Called `unwrap_multi()` on a `Single` value") + } + MappingPatternResult::Multi(mappings) => mappings, + } + } + + pub fn unwrap_single_or(self, or: MappingBuf) -> MappingBuf { + match self { + MappingPatternResult::Single(mapping) => mapping, + MappingPatternResult::Multi(_) => or, + } + } + + pub fn unwrap_multi_or(self, or: Vec) -> Vec { + match self { + MappingPatternResult::Single(_) => or, + MappingPatternResult::Multi(mappings) => mappings, + } + } + + pub fn unwrap_single_or_else(self, or: F) -> MappingBuf + where + F: FnOnce() -> MappingBuf, + { + match self { + MappingPatternResult::Single(mapping) => mapping, + MappingPatternResult::Multi(_) => or(), + } + } + + pub fn unwrap_multi_or_else(self, or: F) -> Vec + where + F: FnOnce() -> Vec, + { + match self { + MappingPatternResult::Single(_) => or(), + MappingPatternResult::Multi(mappings) => mappings, + } + } + + pub fn len(&self) -> usize { + match self { + MappingPatternResult::Single(_) => 1, + MappingPatternResult::Multi(mappings) => mappings.len(), + } + } + + pub fn is_empty(&self) -> bool { + match self { + MappingPatternResult::Single(_) => false, + MappingPatternResult::Multi(mappings) => mappings.len() < 1, + } + } +} + +impl IntoIterator for MappingPatternResult { + type Item = MappingBuf; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + MappingPatternResult::Single(m) => vec![m].into_iter(), + MappingPatternResult::Multi(v) => v.into_iter(), + } + } +} diff --git a/systems/sheet/src/sheet.rs b/systems/sheet/src/sheet.rs new file mode 100644 index 0000000..54420ab --- /dev/null +++ b/systems/sheet/src/sheet.rs @@ -0,0 +1 @@ +pub struct Sheet {} -- cgit