1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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<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 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<str> }
})
.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<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
}
|