summaryrefslogtreecommitdiff
path: root/rola-vcs/internal_macros/src/constants.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-22 22:10:19 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-17 21:02:05 +0800
commit5a5a07c7fad31641d032a743e4e87ffb58ade17d (patch)
treeb6fbd33e8de82e5f9d9e6b99e3cb2102e47fe3ee /rola-vcs/internal_macros/src/constants.rs
Initial commitmain
Diffstat (limited to 'rola-vcs/internal_macros/src/constants.rs')
-rw-r--r--rola-vcs/internal_macros/src/constants.rs115
1 files changed, 115 insertions, 0 deletions
diff --git a/rola-vcs/internal_macros/src/constants.rs b/rola-vcs/internal_macros/src/constants.rs
new file mode 100644
index 0000000..2e76bfe
--- /dev/null
+++ b/rola-vcs/internal_macros/src/constants.rs
@@ -0,0 +1,115 @@
+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<Item> = 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 function.
+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);
+
+ if placeholders.is_empty() {
+ parse_quote! {
+ #(#attrs)*
+ pub fn #name() -> String {
+ #value_str.to_string()
+ }
+ }
+ } else {
+ let params: Vec<_> = placeholders
+ .iter()
+ .map(|p| {
+ let ident = format_ident!("{p}");
+ quote! { #ident: impl ::core::convert::AsRef<str> }
+ })
+ .collect();
+
+ let format_args: Vec<_> = placeholders
+ .iter()
+ .map(|p| {
+ let ident = format_ident!("{p}");
+ quote! { #ident = #ident.as_ref() }
+ })
+ .collect();
+
+ parse_quote! {
+ #(#attrs)*
+ 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<String> {
+ 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
+}