summaryrefslogtreecommitdiff
path: root/systems
diff options
context:
space:
mode:
Diffstat (limited to 'systems')
-rw-r--r--systems/sheet/Cargo.toml5
-rw-r--r--systems/sheet/macros/Cargo.toml12
-rw-r--r--systems/sheet/macros/src/lib.rs374
-rw-r--r--systems/sheet/src/lib.rs6
-rw-r--r--systems/sheet/src/mapping.rs422
-rw-r--r--systems/sheet/src/mapping_pattern.rs173
-rw-r--r--systems/sheet/src/sheet.rs1
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 {}