diff options
Diffstat (limited to 'systems')
| -rw-r--r-- | systems/sheet/Cargo.toml | 5 | ||||
| -rw-r--r-- | systems/sheet/macros/Cargo.toml | 12 | ||||
| -rw-r--r-- | systems/sheet/macros/src/lib.rs | 374 | ||||
| -rw-r--r-- | systems/sheet/src/lib.rs | 6 | ||||
| -rw-r--r-- | systems/sheet/src/mapping.rs | 422 | ||||
| -rw-r--r-- | systems/sheet/src/mapping_pattern.rs | 173 | ||||
| -rw-r--r-- | systems/sheet/src/sheet.rs | 1 |
7 files changed, 992 insertions, 1 deletions
diff --git a/systems/sheet/Cargo.toml b/systems/sheet/Cargo.toml index 89c439b..5c53c36 100644 --- a/systems/sheet/Cargo.toml +++ b/systems/sheet/Cargo.toml @@ -1,6 +1,9 @@ [package] -name = "sheet" +name = "sheet_system" edition = "2024" version.workspace = true [dependencies] +sheet_system_macros = { path = "macros" } +string_proc = { path = "../../utils/string_proc" } +asset_system = { path = "../_asset" } diff --git a/systems/sheet/macros/Cargo.toml b/systems/sheet/macros/Cargo.toml new file mode 100644 index 0000000..d7a59d1 --- /dev/null +++ b/systems/sheet/macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sheet_system_macros" +version.workspace = true +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full", "extra-traits"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/systems/sheet/macros/src/lib.rs b/systems/sheet/macros/src/lib.rs new file mode 100644 index 0000000..c0e936c --- /dev/null +++ b/systems/sheet/macros/src/lib.rs @@ -0,0 +1,374 @@ +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::parse_str; + +const LOCAL_MAPPING_PATH: &str = "just_enough_vcs::system::sheet_system::mapping::LocalMapping"; +const MAPPING_BUF_PATH: &str = "just_enough_vcs::system::sheet_system::mapping::MappingBuf"; +const MAPPING_PATH: &str = "just_enough_vcs::system::sheet_system::mapping::Mapping"; +const LOCAL_MAPPING_FORWARD_PATH: &str = + "just_enough_vcs::system::sheet_system::mapping::LocalMappingForward"; + +/// Parse strings in the format "sheet:/path" +fn parse_sheet_path(input: &str) -> Result<(String, Vec<String>), String> { + let parts: Vec<&str> = input.split(":/").collect(); + if parts.len() != 2 { + return Err(format!( + "Invalid sheet path syntax. Expected: sheet:/path, got: {}", + input + )); + } + + let sheet = parts[0].to_string(); + let path = parts[1]; + + if path.is_empty() { + return Err("Path cannot be empty".to_string()); + } + + let path_parts: Vec<String> = path.split('/').map(|s| s.to_string()).collect(); + + Ok((sheet, path_parts)) +} + +/// Parse strings in the format "id/ver" +fn parse_id_version(input: &str) -> Result<(String, String), String> { + let parts: Vec<&str> = input.split('/').collect(); + if parts.len() != 2 { + return Err(format!( + "Invalid id/version syntax. Expected: id/ver, got: {}", + input + )); + } + + let id = parts[0].trim().to_string(); + let ver = parts[1].trim().to_string(); + + if id.is_empty() { + return Err("ID cannot be empty".to_string()); + } + if ver.is_empty() { + return Err("Version cannot be empty".to_string()); + } + + Ok((id, ver)) +} + +/// Parse a path string into a vector of strings +fn parse_path_string(input: &str) -> Vec<String> { + input.split('/').map(|s| s.trim().to_string()).collect() +} + +/// Generate token stream for path vector +fn path_vec_to_tokens(path_vec: &[String]) -> TokenStream2 { + let path_items: Vec<_> = path_vec.iter().map(|s| quote! { #s.to_string() }).collect(); + + quote! { vec![#(#path_items),*] } +} + +/// Create a MappingBuf +/// +/// Use the following syntax to create a MappingBuf +/// ```ignore +/// let mapping_buf = mapping_buf!( +/// // Map the `version` of index `index_id` +/// // to `your_dir/your_file.suffix` in `your_sheet` +/// "your_sheet:/your_dir/your_file.suffix" => "index_id/version" +/// ); +/// ``` +#[proc_macro] +pub fn mapping_buf(input: TokenStream) -> TokenStream { + let input_str = input.to_string(); + let parts: Vec<&str> = input_str.split("=>").collect(); + + if parts.len() != 2 { + return syn::Error::new( + Span::call_site(), + "Invalid mapping_buf syntax. Expected: mapping_buf!(\"sheet:/path\" => \"id/ver\")", + ) + .to_compile_error() + .into(); + } + + let left = parts[0].trim().trim_matches('"').trim(); + let right = parts[1].trim().trim_matches('"').trim(); + + let (sheet, path_vec) = match parse_sheet_path(left) { + Ok(result) => result, + Err(err) => { + return syn::Error::new(Span::call_site(), err) + .to_compile_error() + .into(); + } + }; + + let (id, ver) = match parse_id_version(right) { + Ok(result) => result, + Err(err) => { + return syn::Error::new(Span::call_site(), err) + .to_compile_error() + .into(); + } + }; + + let path_vec_tokens = path_vec_to_tokens(&path_vec); + + let mapping_buf_path: syn::Path = + parse_str(MAPPING_BUF_PATH).expect("Failed to parse MAPPING_BUF_PATH"); + + let expanded = quote! { + #mapping_buf_path::new( + #sheet.to_string(), + #path_vec_tokens, + #id.to_string(), + #ver.to_string() + ) + }; + + expanded.into() +} + +/// Create a Mapping +/// +/// Use the following syntax to create a Mapping +/// ```ignore +/// let mapping = mapping!( +/// // Map the `version` of index `index_id` +/// // to `your_dir/your_file.suffix` in `your_sheet` +/// "your_sheet:/your_dir/your_file.suffix" => "index_id/version" +/// ); +/// ``` +#[proc_macro] +pub fn mapping(input: TokenStream) -> TokenStream { + let input_str = input.to_string(); + let parts: Vec<&str> = input_str.split("=>").collect(); + + if parts.len() != 2 { + return syn::Error::new( + Span::call_site(), + "Invalid mapping syntax. Expected: mapping!(\"sheet:/path\" => \"id/ver\")", + ) + .to_compile_error() + .into(); + } + + let left = parts[0].trim().trim_matches('"').trim(); + let right = parts[1].trim().trim_matches('"').trim(); + + let (sheet, path_vec) = match parse_sheet_path(left) { + Ok(result) => result, + Err(err) => { + return syn::Error::new(Span::call_site(), err) + .to_compile_error() + .into(); + } + }; + + let (id, ver) = match parse_id_version(right) { + Ok(result) => result, + Err(err) => { + return syn::Error::new(Span::call_site(), err) + .to_compile_error() + .into(); + } + }; + + let path = path_vec.join("/"); + + let mapping_path: syn::Path = parse_str(MAPPING_PATH).expect("Failed to parse MAPPING_PATH"); + + let expanded = quote! { + #mapping_path::new( + #sheet, + #path, + #id, + #ver + ) + }; + + expanded.into() +} + +enum LocalMappingParts { + Latest(String, String, String), + Version(String, String, String), + WithRef(String, String, String, String), +} + +impl LocalMappingParts { + fn parse(input: TokenStream) -> Result<Self, syn::Error> { + let input_str = input.to_string(); + + // LocalMapping does not have a sheet_name definition + // So when the user specifies a sheet prefix, an error should be reported + if input_str.contains(":/") { + return Err(syn::Error::new( + Span::call_site(), + "local_mapping is not related to sheets. Do not use 'sheet:/' prefix.", + )); + } + + // When both "==" and "=>" appear + // It's impossible to determine whether to match the current version or point to a Ref + // Should report an error + if input_str.contains("==") && input_str.contains("=>") { + return Err(syn::Error::new( + Span::call_site(), + "Ambiguous forward direction. Use either '==' for version or '=>' for ref, not both.", + )); + } + + if input_str.contains("==") { + let parts: Vec<&str> = input_str.split("==").collect(); + if parts.len() != 2 { + return Err(syn::Error::new( + Span::call_site(), + "Invalid local_mapping syntax with '=='. Expected: local_mapping!(\"path\" == \"id/ver\")", + )); + } + + let left = parts[0].trim().trim_matches('"').trim(); + let right = parts[1].trim().trim_matches('"').trim(); + + let (id, ver) = + parse_id_version(right).map_err(|err| syn::Error::new(Span::call_site(), err))?; + + return Ok(LocalMappingParts::Version(left.to_string(), id, ver)); + } + + let parts: Vec<&str> = input_str.split("=>").collect(); + + match parts.len() { + 2 => { + // local_mapping!("path" => "id/ver") - Latest + let left = parts[0].trim().trim_matches('"').trim(); + let right = parts[1].trim().trim_matches('"').trim(); + + let (id, ver) = parse_id_version(right) + .map_err(|err| syn::Error::new(Span::call_site(), err))?; + + Ok(LocalMappingParts::Latest(left.to_string(), id, ver)) + } + 3 => { + // local_mapping!("path" => "id/ver" => "ref") - Ref + let left = parts[0].trim().trim_matches('"').trim(); + let middle = parts[1].trim().trim_matches('"').trim(); + let right = parts[2].trim().trim_matches('"').trim(); + + let (id, ver) = parse_id_version(middle) + .map_err(|err| syn::Error::new(Span::call_site(), err))?; + + Ok(LocalMappingParts::WithRef( + left.to_string(), + id, + ver, + right.to_string(), + )) + } + _ => Err(syn::Error::new( + Span::call_site(), + "Invalid local_mapping syntax. Expected: local_mapping!(\"path\" => \"id/ver\") or local_mapping!(\"path\" == \"id/ver\") or local_mapping!(\"path\" => \"id/ver\" => \"ref\")", + )), + } + } +} + +/// Create a LocalMapping +/// +/// Use the following syntax to create a LocalMapping +/// ```ignore +/// let lcoal_mapping_to_latest = local_mapping!( +/// // Map the `version` of index `index_id` +/// // to `your_dir/your_file.suffix` +/// // and expects to keep the latest version +/// "your_dir/your_file.suffix" => "index_id/version" +/// ); +/// +/// let lcoal_mapping_to_version = local_mapping!( +/// // Map the `version` of index `index_id` +/// // to `your_dir/your_file.suffix` +/// // and expects to keep the current version +/// "your_dir/your_file.suffix" == "index_id/version" +/// ); +/// +/// let lcoal_mapping_latest = local_mapping!( +/// // Map the `version` of index `index_id` +/// // to `your_dir/your_file.suffix` +/// // and expects to match the version declared in `ref` +/// "your_dir/your_file.suffix" => "index_id/version" => "ref" +/// ); +/// ``` +#[proc_macro] +pub fn local_mapping(input: TokenStream) -> TokenStream { + let parts = match LocalMappingParts::parse(input) { + Ok(parts) => parts, + Err(err) => return err.to_compile_error().into(), + }; + + match parts { + LocalMappingParts::Latest(path_str, id, ver) => { + let path_vec = parse_path_string(&path_str); + let path_vec_tokens = path_vec_to_tokens(&path_vec); + + let local_mapping_path: syn::Path = + parse_str(LOCAL_MAPPING_PATH).expect("Failed to parse LOCAL_MAPPING_PATH"); + let local_mapping_forward_path: syn::Path = parse_str(LOCAL_MAPPING_FORWARD_PATH) + .expect("Failed to parse LOCAL_MAPPING_FORWARD_PATH"); + + let expanded = quote! { + #local_mapping_path::new( + #path_vec_tokens, + #id.to_string(), + #ver.to_string(), + #local_mapping_forward_path::Latest + ) + }; + + expanded.into() + } + LocalMappingParts::Version(path_str, id, ver) => { + let path_vec = parse_path_string(&path_str); + let path_vec_tokens = path_vec_to_tokens(&path_vec); + + let local_mapping_path: syn::Path = + parse_str(LOCAL_MAPPING_PATH).expect("Failed to parse LOCAL_MAPPING_PATH"); + let local_mapping_forward_path: syn::Path = parse_str(LOCAL_MAPPING_FORWARD_PATH) + .expect("Failed to parse LOCAL_MAPPING_FORWARD_PATH"); + + let expanded = quote! { + #local_mapping_path::new( + #path_vec_tokens, + #id.to_string(), + #ver.to_string(), + #local_mapping_forward_path::Version { + version_name: #ver.to_string() + } + ) + }; + + expanded.into() + } + LocalMappingParts::WithRef(path_str, id, ver, ref_name) => { + let path_vec = parse_path_string(&path_str); + let path_vec_tokens = path_vec_to_tokens(&path_vec); + + let local_mapping_path: syn::Path = + parse_str(LOCAL_MAPPING_PATH).expect("Failed to parse LOCAL_MAPPING_PATH"); + let local_mapping_forward_path: syn::Path = parse_str(LOCAL_MAPPING_FORWARD_PATH) + .expect("Failed to parse LOCAL_MAPPING_FORWARD_PATH"); + + let expanded = quote! { + #local_mapping_path::new( + #path_vec_tokens, + #id.to_string(), + #ver.to_string(), + #local_mapping_forward_path::Ref { + sheet_name: #ref_name.to_string() + } + ) + }; + + expanded.into() + } + } +} 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<String>, + + /// 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<String>, + 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<String>, + id: impl Into<String>, + ver: impl Into<String>, + forward: LocalMappingForward, + ) -> Self { + Self { + val, + id: id.into(), + ver: ver.into(), + forward, + } + } + + /// Get the path value of LocalMapping + pub fn value(&self) -> &Vec<String> { + &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<String>) -> 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<String>) -> 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<String>, + val: Vec<String>, + id: impl Into<String>, + ver: impl Into<String>, + ) -> 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<String> { + &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<String> { + 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<Mapping<'a>> 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<String>) -> 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<String>) { + self.sheet_name = sheet_name.into(); + } + + /// Set the value of the current MappingBuf + pub fn set_value(&mut self, val: Vec<String>) { + 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<String>) { + self.id = id.into(); + } + + /// Set the mapped index version of the current MappingBuf + pub fn set_mapped_version(&mut self, version: impl Into<String>) { + 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<String>) -> 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<String>) { + self.val = val; + } + + /// Set the mapped index ID of the current LocalMapping + pub fn set_mapped_id(&mut self, id: impl Into<String>) { + self.id = id.into(); + } + + /// Set the mapped index version of the current LocalMapping + pub fn set_mapped_version(&mut self, version: impl Into<String>) { + 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<String>) -> Vec<String> { + 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<Mapping<'a>> 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<LocalMapping> for Mapping<'a> { + fn eq(&self, other: &LocalMapping) -> bool { + other == self + } +} + +impl PartialEq<MappingBuf> for LocalMapping { + fn eq(&self, other: &MappingBuf) -> bool { + self.val == other.val && self.id == other.id && self.ver == other.ver + } +} + +impl PartialEq<LocalMapping> for MappingBuf { + fn eq(&self, other: &LocalMapping) -> bool { + other == self + } +} + +impl<'a> PartialEq<MappingBuf> 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<Mapping<'a>> 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<MappingBuf>), +} + +impl MappingPatternResult { + pub fn new_single(mapping: MappingBuf) -> Self { + Self::Single(mapping) + } + + pub fn new_multi(mappings: Vec<MappingBuf>) -> 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<MappingBuf> { + match self { + MappingPatternResult::Single(mapping) => Some(mapping), + MappingPatternResult::Multi(_) => None, + } + } + + pub fn multi(self) -> Option<Vec<MappingBuf>> { + match self { + MappingPatternResult::Single(_) => None, + MappingPatternResult::Multi(mappings) => Some(mappings), + } + } + + pub fn ensure_multi(self) -> Vec<MappingBuf> { + 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<MappingBuf> { + 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<MappingBuf>) -> Vec<MappingBuf> { + match self { + MappingPatternResult::Single(_) => or, + MappingPatternResult::Multi(mappings) => mappings, + } + } + + pub fn unwrap_single_or_else<F>(self, or: F) -> MappingBuf + where + F: FnOnce() -> MappingBuf, + { + match self { + MappingPatternResult::Single(mapping) => mapping, + MappingPatternResult::Multi(_) => or(), + } + } + + pub fn unwrap_multi_or_else<F>(self, or: F) -> Vec<MappingBuf> + where + F: FnOnce() -> Vec<MappingBuf>, + { + 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<MappingBuf>; + + 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 {} |
