diff options
| -rw-r--r-- | Cargo.lock | 265 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/lib.rs | 8 | ||||
| -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 |
10 files changed, 1249 insertions, 19 deletions
@@ -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" @@ -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" } @@ -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 {} |
