blob: e9163114d20e66d0ae2cf8c09e6cd008b723ad5c (
plain)
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
123
124
125
126
127
128
129
130
|
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::ParseStream;
use syn::{Attribute, DeriveInput, Expr, parse_macro_input};
/// # Macro - ConfigFile
///
/// ## Usage
///
/// Use `#[derive(ConfigFile)]` to derive the ConfigFile trait for a struct
///
/// Specify the default storage path via `#[cfg_file(path = "...")]`
///
/// ## About the `cfg_file` attribute macro
///
/// Use `#[cfg_file(path = "string")]` to specify the configuration file path
///
/// Or use `#[cfg_file(path = constant_expression)]` to specify the configuration file path
///
/// ## Path Rules
///
/// Paths starting with `"./"`: relative to the current working directory
///
/// Other paths: treated as absolute paths
///
/// When no path is specified: use the struct name + ".json" as the default filename (e.g., `my_struct.json`)
///
/// ## Example
/// ```ignore
/// #[derive(ConfigFile)]
/// #[cfg_file(path = "./config.json")]
/// struct AppConfig;
/// ```
#[proc_macro_derive(ConfigFile, attributes(cfg_file))]
pub fn derive_config_file(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// Process 'cfg_file'
let path_expr = match find_cfg_file_path(&input.attrs) {
Some(PathExpr::StringLiteral(path)) => {
if let Some(path_str) = path.strip_prefix("./") {
quote! {
std::env::current_dir()?.join(#path_str)
}
} else {
// Using Absolute Path
quote! {
std::path::PathBuf::from(#path)
}
}
}
Some(PathExpr::PathExpression(path_expr)) => {
// For path expressions (constants), generate code that references the constant
quote! {
std::path::PathBuf::from(#path_expr)
}
}
None => {
let default_file = to_snake_case(&name.to_string()) + ".json";
quote! {
std::env::current_dir()?.join(#default_file)
}
}
};
let expanded = quote! {
impl cfg_file::config::ConfigFile for #name {
type DataType = #name;
fn default_path() -> Result<std::path::PathBuf, std::io::Error> {
Ok(#path_expr)
}
}
};
TokenStream::from(expanded)
}
enum PathExpr {
StringLiteral(String),
PathExpression(syn::Expr),
}
fn find_cfg_file_path(attrs: &[Attribute]) -> Option<PathExpr> {
for attr in attrs {
if attr.path().is_ident("cfg_file") {
let parser = |meta: ParseStream| {
let path_meta: syn::MetaNameValue = meta.parse()?;
if path_meta.path.is_ident("path") {
match &path_meta.value {
// String literal case: path = "./vault.toml"
Expr::Lit(expr_lit) if matches!(expr_lit.lit, syn::Lit::Str(_)) => {
if let syn::Lit::Str(lit_str) = &expr_lit.lit {
return Ok(PathExpr::StringLiteral(lit_str.value()));
}
}
// Path expression case: path = SERVER_FILE_VAULT or crate::constants::SERVER_FILE_VAULT
expr @ (Expr::Path(_) | Expr::Macro(_)) => {
return Ok(PathExpr::PathExpression(expr.clone()));
}
_ => {}
}
}
Err(meta.error("expected `path = \"...\"` or `path = CONSTANT`"))
};
if let Ok(path_expr) = attr.parse_args_with(parser) {
return Some(path_expr);
}
}
}
None
}
fn to_snake_case(s: &str) -> String {
let mut snake = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i != 0 {
snake.push('_');
}
snake.push(c.to_ascii_lowercase());
} else {
snake.push(c);
}
}
snake
}
|