diff options
| -rw-r--r-- | Cargo.lock | 57 | ||||
| -rw-r--r-- | Cargo.toml | 11 | ||||
| -rw-r--r-- | src/lib.rs | 4 | ||||
| -rw-r--r-- | systems/_constants/Cargo.toml | 7 | ||||
| -rw-r--r-- | systems/_constants/macros/Cargo.toml | 15 | ||||
| -rw-r--r-- | systems/_constants/macros/src/lib.rs | 189 | ||||
| -rw-r--r-- | systems/_constants/src/lib.rs | 147 |
7 files changed, 429 insertions, 1 deletions
@@ -51,6 +51,15 @@ dependencies = [ ] [[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -281,6 +290,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] +name = "constants" +version = "0.1.0" +dependencies = [ + "macros", +] + +[[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -671,6 +687,7 @@ dependencies = [ "action_system", "cfg_file", "chrono", + "constants", "data_struct", "sha1_hash", "string_proc", @@ -729,6 +746,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "string_proc", + "syn", +] + +[[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1034,6 +1062,35 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -38,12 +38,15 @@ members = [ "systems/action", "systems/action/action_macros", + "systems/_constants", + "systems/_constants/macros", + "legacy_data", "legacy_data/tests", "legacy_actions", - "docs", + "docs", "systems/_constants", ] [workspace.package] @@ -73,14 +76,20 @@ chrono = "0.4" toml = "0.9" [dependencies] +# Utils cfg_file = { path = "utils/cfg_file" } data_struct = { path = "utils/data_struct" } sha1_hash = { path = "utils/sha1_hash" } tcp_connection = { path = "utils/tcp_connection" } string_proc = { path = "utils/string_proc" } +# Systems action_system = { path = "systems/action" } +constants = { path = "systems/_constants" } +# Documents vcs_docs = { path = "docs" } + +# Legacy vcs_data = { path = "legacy_data" } vcs_actions = { path = "legacy_actions" } @@ -18,6 +18,10 @@ pub mod system { pub mod action_system { pub use action_system::*; } + + pub mod constants { + pub use constants::*; + } } pub mod utils { diff --git a/systems/_constants/Cargo.toml b/systems/_constants/Cargo.toml new file mode 100644 index 0000000..c57049c --- /dev/null +++ b/systems/_constants/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "constants" +edition = "2024" +version.workspace = true + +[dependencies] +macros = { path = "macros" } diff --git a/systems/_constants/macros/Cargo.toml b/systems/_constants/macros/Cargo.toml new file mode 100644 index 0000000..04ad4fc --- /dev/null +++ b/systems/_constants/macros/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full", "extra-traits"] } +quote = "1.0" +proc-macro2 = "1.0" +regex = "1.12" + +string_proc = { path = "../../../utils/string_proc" } diff --git a/systems/_constants/macros/src/lib.rs b/systems/_constants/macros/src/lib.rs new file mode 100644 index 0000000..ebcc31f --- /dev/null +++ b/systems/_constants/macros/src/lib.rs @@ -0,0 +1,189 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Expr, ExprLit, Ident, Item, ItemMod, Lit, LitStr, parse_macro_input}; + +#[proc_macro_attribute] +pub fn constants(attr: TokenStream, item: TokenStream) -> TokenStream { + let prefix = parse_macro_input!(attr as LitStr).value(); + let mut input_mod = parse_macro_input!(item as ItemMod); + + if let Some((_, items)) = &mut input_mod.content { + // Collect functions to generate + let mut generated_functions = Vec::new(); + + for item in items.iter_mut() { + if let Item::Macro(macro_item) = item { + // Check if it's a c! macro call + if macro_item.mac.path.is_ident("c") { + // Parse macro content + let macro_tokens = macro_item.mac.tokens.clone(); + let parsed: syn::Result<Expr> = syn::parse2(macro_tokens); + + if let Ok(Expr::Assign(assign)) = parsed { + // Get constant name and value + if let Some((const_name, const_value)) = + extract_const_name_and_value(&assign) + { + // Process constant and generate function + if let Some(func) = process_constant(&prefix, const_name, const_value) { + generated_functions.push(func); + } + } + } + } + } + } + + for func in generated_functions { + items.push(Item::Verbatim(func)); + } + } + + TokenStream::from(quote! { #input_mod }) +} + +fn extract_const_name_and_value(assign: &syn::ExprAssign) -> Option<(Ident, Box<Expr>)> { + if let Expr::Path(path) = &*assign.left { + if let Some(ident) = path.path.get_ident() { + let const_name = ident.clone(); + let const_value = assign.right.clone(); + return Some((const_name, const_value)); + } + } + None +} + +fn process_constant( + prefix: &str, + const_name: Ident, + const_value: Box<Expr>, +) -> Option<proc_macro2::TokenStream> { + if let Expr::Lit(ExprLit { + lit: Lit::Str(lit_str), + .. + }) = *const_value + { + let value_str = lit_str.value(); + let value_span = lit_str.span(); + + if value_str.contains('{') && value_str.contains('}') { + let params = extract_placeholder_params(&value_str); + + if !params.is_empty() { + // With parameters + Some(generate_function_with_params( + prefix, + &const_name, + &value_str, + ¶ms, + value_span, + )) + } else { + // Without parameters + Some(generate_function_without_params( + prefix, + &const_name, + &value_str, + )) + } + } else { + // Without parameters + Some(generate_function_without_params( + prefix, + &const_name, + &value_str, + )) + } + } else { + None + } +} + +fn extract_placeholder_params(value_str: &str) -> Vec<String> { + let mut params = Vec::new(); + let chars = value_str.chars().collect::<Vec<_>>(); + let mut i = 0; + + while i < chars.len() { + if chars[i] == '{' { + let start = i; + i += 1; + + while i < chars.len() && chars[i] != '}' { + i += 1; + } + + if i < chars.len() && chars[i] == '}' { + let param: String = chars[start + 1..i].iter().collect(); + if !param.is_empty() { + params.push(param); + } + } + } + i += 1; + } + + params +} + +fn generate_function_with_params( + prefix: &str, + const_name: &Ident, + value_str: &str, + params: &[String], + value_span: proc_macro2::Span, +) -> proc_macro2::TokenStream { + let fn_name = format!("{}_{}", prefix, const_name.to_string().to_lowercase()); + let fn_ident = Ident::new(&fn_name, const_name.span()); + + // Generate parameter list + let param_idents: Vec<Ident> = params.iter().map(|p| Ident::new(p, value_span)).collect(); + let format_args = param_idents.iter().map(|ident| quote! { #ident }); + + // Generate format! code + let doc_format_vaule = params.join(", "); + + let mut doc_format_template = value_str.to_string(); + for param in params { + let placeholder = format!("{{{}}}", param); + doc_format_template = doc_format_template.replace(&placeholder, "{}"); + } + + let doc_format_code = format!( + "format!(\"{}\", {});", + doc_format_template, doc_format_vaule + ); + + // Replace {xxx} with {} format + let mut format_str = value_str.to_string(); + for param in params { + format_str = format_str.replace(&format!("{{{}}}", param), "{}"); + } + + quote! { + #[doc = "```ignore"] + #[doc = #doc_format_code] + #[doc = "```"] + pub fn #fn_ident(#(#param_idents: &str),*) -> String { + format!(#format_str, #(#format_args),*) + } + } +} + +fn generate_function_without_params( + prefix: &str, + const_name: &Ident, + value_str: &str, +) -> proc_macro2::TokenStream { + let fn_name = format!("{}_{}", prefix, const_name.to_string().to_lowercase()); + let fn_ident = Ident::new(&fn_name, const_name.span()); + + quote! { + #[doc = "`"] + #[doc = #value_str] + #[doc = "`"] + pub fn #fn_ident() -> &'static str { + #value_str + } + } +} diff --git a/systems/_constants/src/lib.rs b/systems/_constants/src/lib.rs new file mode 100644 index 0000000..e73f81f --- /dev/null +++ b/systems/_constants/src/lib.rs @@ -0,0 +1,147 @@ +#[macro_export] +macro_rules! c { + ($name:ident = $value:expr) => { + const $name: &str = $value; + }; +} + +/// File and directory path constants for the server root +#[allow(unused)] +pub mod server { + /// File constants + #[macros::constants("server_file")] + pub mod files { + c! { CONFIG = "./config.toml" } + c! { JOIN_REQUEST_KEY = "./.temp/join_requests/{member_name}.pem" } + c! { KEY = "./keys/{member_name}.pem" } + c! { MEMBER_METADATA = "./meta/{member_name}.toml" } + } + + /// Directory path constants + #[macros::constants("server_dir")] + pub mod dirs { + c! { KEYS = "./key/" } + c! { JOIN_REQUEST_KEYS = "./.temp/join_requests/" } + c! { MEMBER_METADATAS = "./meta/" } + c! { VAULTS = "./v/" } + } +} + +/// File and directory path constants for the vault root +#[allow(unused)] +pub mod vault { + /// File path constants + #[macros::constants("vault_file")] + pub mod files { + // ### Configs ### + c! { CONFIG = "./vault.toml" } + + // ### Sheets ### + + // Reference sheet + c! { REFSHEET = "./ref/{ref_sheet_name}.sheet" } + + // Editable instance of a reference sheet, maintained by one of the vault's Hosts, written back to the sheet file after editing + c! { REFSHEET_EDIT = "./ref/~{ref_sheet_name}.sheet" } + + // Member sheet backup, only used to temporarily store a member's local workspace file structure, fully controlled by the corresponding member + c! { MEMBER_SHEET_BACKUP = "./_member/{member_name}/backups/{sheet_name}.sheet" } + + // Share sent to a reference sheet, selectively merged into that reference sheet by a Host + c! { SHARE_TO_REF = "./req/{ref_sheet_name}/{share_id}.sheet" } + + // Share sent to a specific member, fully controlled by that member, who can optionally merge it into any of their own sheets + c! { SHARE_TO_MEMBER = "./_member/{member_name}/shares/{share_id}.sheet" } + + // ### Storages ### + c! { CHANGE_FILE = "./changes/CURRENT" } + c! { CHANGE_FILE_BACKUP = "./changes/backup.{index}" } + + // Whether an index is locked; if the file exists, the member name written inside is the current holder + c! { INDEX_LOCK = "./index/{index_slice_1}/{index_slice_2}/{index_name}/LOCK" } + + // Editable instance of an index's version sequence, maintained by the holder, written back to the ver file after editing + c! { INDEX_VER_EDIT = "./index/{index_slice_1}/{index_slice_2}/{index_name}/~ver" } + + // Index version sequence + c! { INDEX_VER = "./index/{index_slice_1}/{index_slice_2}/{index_name}/ver" } + + // Index + c! { INDEX = "./index/{index_slice_1}/{index_slice_2}/{index_name}/{version}.index" } + + // Object, file chunk + c! { OBJ = "./obj/{obj_hash_slice_1}/{obj_hash_slice_2}/{obj_hash}" } + } + + /// Directory path constants + #[macros::constants("vault_dir")] + pub mod dirs { + c! { REFSHEETS = "./ref/" } + c! { SHARES_TO_REF = "./req/{ref_sheet_name}/" } + c! { MEMBER = "./_member/{member_name}/" } + c! { MEMBER_SHEET_BACKUPS = "./_member/{member_name}/backups/" } + c! { MEMBER_SHARES = "./_member/{member_name}/shares/" } + c! { CHANGES = "./changes/" } + } +} + +/// File and directory path constants for the workspace root +#[allow(unused)] +pub mod workspace { + /// File path constants + #[macros::constants("workspace_file")] + pub mod files { + // ### Config ### + c! { CONFIG = "./.jv/workspace.toml" } + + // ### Sheets ### + // Records the latest state of local physical files, used to calculate deviations + c! { LOCAL_STATUS = "./.jv/sheets/{account}/{sheet}.local" } + + // Personal sheet, represents the desired file structure, held only by the member, can be backed up to the vault + c! { SHEET = "./.jv/sheets/{account}/{sheet}.sheet" } + + // Draft file, when switching to another sheet, fully records modified but untracked files + c! { DRAFTED_FILE = "./.jv/drafts/{account}_{sheet}/{mapping}" } + + // Working file + c! { WORKING_FILE = "./{mapping}" } + } + + /// Directory path constants + #[macros::constants("workspace_dir")] + pub mod dirs { + c! { WORKSPACE = "./.jv" } + c! { VAULT_MIRROR = "./.jv/UPSTREAM/" } + c! { LOCAL_SHEETS = "./.jv/sheets/{account}/" } + c! { DRAFT_AREA = "./.jv/drafts/{account}_{sheet}/" } + c! { WORKING_AREA = "./" } + } +} + +/// File and directory path constants for the user root +#[allow(unused)] +pub mod user { + /// File path constants + #[macros::constants("user_file")] + pub mod files { + // Upstream public key, stored after initial login, used to verify the trustworthiness of that upstream + c! { UPSTREAM_PUB = "./upstreams/{upstream_addr}.pem" } + + // Account private key, stored only locally, used for login authentication + c! { PRIVATE_KEY = "./private/{account}.pem" } + + // Account public key, automatically generated from the private key and stored, + // will be placed into the server's "join request list" upon initial login (if server.toml permits this action) + // The server administrator can optionally approve this request + c! { PUBLIC_KEY = "./public/{account}.pem" } + } + + /// Directory path constants + #[macros::constants("user_dir")] + pub mod dirs { + c! { UPSTREAM_PUBS = "./upstreams/" } + c! { PRIVATE_KEYS = "./private/" } + c! { PUBLIC_KEYS = "./public/" } + } +} |
