summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-02-12 04:42:10 +0800
committer魏曹先生 <1992414357@qq.com>2026-02-12 05:07:50 +0800
commiteec323baf28f1a588f835aa773b77e019f91446d (patch)
treeff186f5c1094374101d58caee1cc7d1b8c72dddc
parentce1a5ac5c4c116e28f647549f422f8bdbd94487c (diff)
Add sheet system with mapping macros and modules
-rw-r--r--Cargo.lock265
-rw-r--r--Cargo.toml2
-rw-r--r--src/lib.rs8
-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
10 files changed, 1249 insertions, 19 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 62f0c72..37257a7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -34,7 +34,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
- "cpufeatures",
+ "cpufeatures 0.2.17",
]
[[package]]
@@ -69,6 +69,12 @@ dependencies = [
]
[[package]]
+name = "anyhow"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
+
+[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -275,13 +281,13 @@ dependencies = [
[[package]]
name = "chacha20"
-version = "0.10.0-rc.2"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bd162f2b8af3e0639d83f28a637e4e55657b7a74508dba5a9bf4da523d5c9e9"
+checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
- "cpufeatures",
- "rand_core 0.9.3",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.0",
]
[[package]]
@@ -354,6 +360,15 @@ dependencies = [
]
[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "crc"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -394,7 +409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"curve25519-dalek-derive",
"digest 0.11.0-rc.3",
"fiat-crypto",
@@ -517,6 +532,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -639,12 +660,41 @@ dependencies = [
]
[[package]]
+name = "getrandom"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "rand_core 0.10.0",
+ "wasip2",
+ "wasip3",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash",
+]
+
+[[package]]
name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -687,13 +737,21 @@ dependencies = [
]
[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
+[[package]]
name = "indexmap"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
- "hashbrown",
+ "hashbrown 0.16.0",
+ "serde",
+ "serde_core",
]
[[package]]
@@ -734,6 +792,7 @@ dependencies = [
"data_struct",
"jvlib",
"sha1_hash",
+ "sheet_system",
"string_proc",
"tcp_connection",
"toml",
@@ -760,6 +819,12 @@ dependencies = [
]
[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -991,6 +1056,16 @@ dependencies = [
]
[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1036,12 +1111,13 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.10.0-rc.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ec474812b9de55111b29da8a1559f1718ef3dc20fa36f031f1b5d9e3836ad6c"
+checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"chacha20",
- "rand_core 0.9.3",
+ "getrandom 0.4.1",
+ "rand_core 0.10.0",
]
[[package]]
@@ -1083,6 +1159,12 @@ dependencies = [
]
[[package]]
+name = "rand_core"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
+
+[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1335,7 +1417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest 0.10.7",
]
@@ -1355,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest 0.10.7",
]
@@ -1366,13 +1448,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest 0.11.0-rc.3",
]
[[package]]
-name = "sheet"
+name = "sheet_system"
+version = "0.1.0"
+dependencies = [
+ "asset_system",
+ "sheet_system_macros",
+ "string_proc",
+]
+
+[[package]]
+name = "sheet_system_macros"
version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
[[package]]
name = "shlex"
@@ -1485,7 +1581,7 @@ dependencies = [
"crc",
"ed25519-dalek",
"pem",
- "rand 0.10.0-rc.0",
+ "rand 0.10.0",
"ring",
"rmp-serde",
"rsa",
@@ -1625,6 +1721,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1739,7 +1841,16 @@ version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
- "wit-bindgen",
+ "wit-bindgen 0.46.0",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen 0.51.0",
]
[[package]]
@@ -1808,6 +1919,40 @@ dependencies = [
]
[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "semver",
+]
+
+[[package]]
name = "web-sys"
version = "0.3.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2087,6 +2232,94 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "prettyplease",
+ "syn",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags",
+ "indexmap",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
+[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 943c4d7..aea255b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,6 +35,7 @@ members = [
"systems/action",
"systems/action/action_macros",
"systems/sheet",
+ "systems/sheet/macros",
"utils/cfg_file",
"utils/cfg_file/cfg_file_derive",
"utils/cfg_file/cfg_file_test",
@@ -95,6 +96,7 @@ string_proc = { path = "utils/string_proc" }
asset_system = { path = "systems/_asset" }
constants = { path = "systems/_constants" }
action_system = { path = "systems/action" }
+sheet_system = { path = "systems/sheet" }
# Legacy
vcs_data = { path = "legacy_data" }
diff --git a/src/lib.rs b/src/lib.rs
index cb8fde1..a01e5e4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,12 +20,16 @@ pub mod system {
pub use constants::*;
}
+ pub mod asset_system {
+ pub use asset_system::*;
+ }
+
pub mod action_system {
pub use action_system::*;
}
- pub mod asset_system {
- pub use asset_system::*;
+ pub mod sheet_system {
+ pub use sheet_system::*;
}
}
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 {}