From 0b8e6e7d18abb94bd99553dc1d2b0ba5d4f265ea Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 18 Jun 2026 02:47:32 +0800 Subject: refactor: extract shared utilities and add space-system crate Extract rola-vcs/internal_macros into shared utils crates (shared_constants, shared_macros, space-system) and implement the Bucket enum with async space management --- rola-utils/macros/src/constants.rs | 122 +++++++++++++++++++++++++++++++++++++ rola-utils/macros/src/lib.rs | 38 ++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 rola-utils/macros/src/constants.rs (limited to 'rola-utils/macros') diff --git a/rola-utils/macros/src/constants.rs b/rola-utils/macros/src/constants.rs new file mode 100644 index 0000000..e5fe668 --- /dev/null +++ b/rola-utils/macros/src/constants.rs @@ -0,0 +1,122 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{Expr, Item, ItemConst, ItemMod, Lit, parse_macro_input, parse_quote}; + +/// Entry point called from lib.rs. +pub fn expand(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut input_mod = parse_macro_input!(item as ItemMod); + + let (_, items) = match &mut input_mod.content { + Some(content) => content, + None => panic!("#[constants] can only be applied to a module with a body"), + }; + + let mut new_items: Vec = Vec::with_capacity(items.len()); + + for item in items.iter() { + if let Item::Const(const_item) = item { + let func = transform_const(const_item); + new_items.push(func); + } else { + new_items.push(item.clone()); + } + } + + let mod_ident = &input_mod.ident; + let vis = &input_mod.vis; + + let output = quote! { + #[allow(non_snake_case)] + #vis mod #mod_ident { + #(#new_items)* + } + }; + + output.into() +} + +/// Transforms a single `const` item into a functionif. +fn transform_const(const_item: &ItemConst) -> Item { + let name = &const_item.ident; + let attrs = &const_item.attrs; + + // Extract the string literal value from the const + let value_str = match &*const_item.expr { + Expr::Lit(expr_lit) => match &expr_lit.lit { + Lit::Str(lit_str) => lit_str.value(), + _ => panic!( + "#[constants] only supports `&str` literals, \ + but `{name}` has a non-string literal" + ), + }, + _ => panic!( + "#[constants] only supports literal expressions, \ + but `{name}` has a non-literal expression" + ), + }; + + let placeholders = extract_placeholders(&value_str); + + // Build a doc comment that shows the original constant value + let doc_comment = format!( + "Generated from const `{}` with value: \"{}\"", + name, + value_str.replace('\"', "\\\"") + ); + + if placeholders.is_empty() { + parse_quote! { + #(#attrs)* + #[doc = #doc_comment] + pub const #name: &'static str = #value_str; + } + } else { + let params: Vec<_> = placeholders + .iter() + .map(|p| { + let ident = format_ident!("{p}"); + quote! { #ident: impl ::core::convert::AsRef } + }) + .collect(); + + let format_args: Vec<_> = placeholders + .iter() + .map(|p| { + let ident = format_ident!("{p}"); + quote! { #ident = #ident.as_ref() } + }) + .collect(); + + parse_quote! { + #(#attrs)* + #[doc = #doc_comment] + pub fn #name(#(#params),*) -> String { + ::std::format!(#value_str, #(#format_args),*) + } + } + } +} + +/// Extracts all `{name}` placeholder identifiers from a format string. +fn extract_placeholders(s: &str) -> Vec { + let mut placeholders = Vec::new(); + let mut chars = s.char_indices().peekable(); + + while let Some((_, c)) = chars.next() { + if c == '{' { + let mut name = String::new(); + for (_, c) in &mut chars { + if c == '}' { + break; + } + name.push(c); + } + let trimmed = name.trim().to_string(); + if !trimmed.is_empty() { + placeholders.push(trimmed); + } + } + } + + placeholders +} diff --git a/rola-utils/macros/src/lib.rs b/rola-utils/macros/src/lib.rs index 8b13789..46762e3 100644 --- a/rola-utils/macros/src/lib.rs +++ b/rola-utils/macros/src/lib.rs @@ -1 +1,39 @@ +use proc_macro::TokenStream; +mod constants; + +/// Transforms `pub const` items in a module into equivalent functions. +/// +/// Constants without `{param}` placeholders become `fn NAME() -> String`. +/// Constants with `{param}` placeholders become `fn NAME(param: impl AsRef) -> String`, +/// using `format!()` to fill in the placeholders. +/// +/// The entire module is annotated with `#[allow(non_snake_case)]`. +/// +/// # Example +/// +/// ```ignore +/// #[rorolala_internal_macros::constants] +/// pub mod paths { +/// pub const ROLA_DRAFT_DIR: &str = ".rola"; +/// pub const ROLA_BINDED_BUCKET_FILE: &str = ".rola/BIND/{bucket}"; +/// } +/// ``` +/// +/// expands to: +/// +/// ```ignore +/// #[allow(non_snake_case)] +/// pub mod paths { +/// pub fn ROLA_DRAFT_DIR() -> String { +/// ".rola".to_string() +/// } +/// pub fn ROLA_BINDED_BUCKET_FILE(bucket: impl AsRef) -> String { +/// format!(".rola/BIND/{bucket}", bucket = bucket.as_ref()) +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn constants(attr: TokenStream, item: TokenStream) -> TokenStream { + constants::expand(attr, item) +} -- cgit