1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput};
4
5fn generate_impl(
6 struct_name: &syn::Ident,
7 trait_name: &str,
8 ext_name: &str,
9) -> proc_macro2::TokenStream {
10 let format_mod = syn::Ident::new(ext_name, proc_macro2::Span::call_site());
11 let trait_ident = syn::Ident::new(trait_name, proc_macro2::Span::call_site());
12 let to_string_fn = syn::Ident::new(
13 if ext_name == "yaml" {
14 "to_string"
15 } else {
16 "to_string_pretty"
17 },
18 proc_macro2::Span::call_site(),
19 );
20
21 let path_method = if cfg!(feature = "dirs") {
22 quote! {
23 fn path() -> std::result::Result<std::path::PathBuf, derive_config::ConfigError> {
24 let path = derive_config::dirs::config_dir().ok_or(derive_config::ConfigError::None)?;
25 let name = env!("CARGO_PKG_NAME");
26 let file = format!("{}.{}", name, #ext_name);
27
28 Ok(path.join(file))
29 }
30 }
31 } else {
32 quote! {
33 fn path() -> std::result::Result<std::path::PathBuf, derive_config::ConfigError> {
34 let mut path = std::env::current_exe()?;
35 path.set_file_name(env!("CARGO_CRATE_NAME"));
36 path.set_extension(#ext_name);
37
38 Ok(path)
39 }
40 }
41 };
42
43 quote! {
44 impl #trait_ident for #struct_name {
45 #path_method
46
47 fn save(&self) -> std::result::Result<(), derive_config::ConfigError> {
48 use std::io::Write;
49
50 let path = Self::path()?;
51 let mut file = std::fs::File::options()
52 .write(true)
53 .create(true)
54 .truncate(true)
55 .open(path)?;
56
57 let content = derive_config::#format_mod::#to_string_fn(&self)?;
58 file.write_all(content.as_bytes())?;
59
60 Ok(())
61 }
62
63 fn and_save(self) -> std::result::Result<Self, derive_config::ConfigError> {
64 self.save()?;
65 Ok(self)
66 }
67
68 fn load() -> std::result::Result<Self, derive_config::ConfigError> {
69 use std::io::{Read, Seek};
70
71 let path = Self::path()?;
72 let mut file = std::fs::File::open(&path)?;
73 let mut text = String::new();
74 file.read_to_string(&mut text)?;
75 file.rewind()?;
76
77 let config = derive_config::#format_mod::from_str(&text)?;
78
79 Ok(config)
80 }
81 }
82 }
83}
84
85fn derive_config(input: TokenStream, trait_name: &str, ext_name: &str) -> TokenStream {
86 let input = parse_macro_input!(input as DeriveInput);
87 let struct_name = input.ident;
88
89 let expanded = generate_impl(&struct_name, trait_name, ext_name);
90
91 TokenStream::from(expanded)
92}
93
94#[cfg(feature = "json")]
95#[proc_macro_derive(DeriveJsonConfig)]
96pub fn derive_json_config(input: TokenStream) -> TokenStream {
97 derive_config(input, "DeriveJsonConfig", "json")
98}
99
100#[cfg(feature = "toml")]
101#[proc_macro_derive(DeriveTomlConfig)]
102pub fn derive_toml_config(input: TokenStream) -> TokenStream {
103 derive_config(input, "DeriveTomlConfig", "toml")
104}
105
106#[cfg(feature = "yaml")]
107#[proc_macro_derive(DeriveYamlConfig)]
108pub fn derive_yaml_config(input: TokenStream) -> TokenStream {
109 derive_config(input, "DeriveYamlConfig", "yaml")
110}