Skip to main content

derive_macros/
lib.rs

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 = "directories") {
22        quote! {
23            fn path() -> std::result::Result<std::path::PathBuf, derive_config::ConfigError> {
24                let base_dirs = derive_config::directories::BaseDirs::new().ok_or(derive_config::ConfigError::None)?;
25                let path = base_dirs.config_dir();
26                let name = env!("CARGO_PKG_NAME");
27                let file = format!("{}.{}", name, #ext_name);
28
29                Ok(path.join(file))
30            }
31        }
32    } else if cfg!(feature = "dirs") {
33        quote! {
34            fn path() -> std::result::Result<std::path::PathBuf, derive_config::ConfigError> {
35                let path = derive_config::dirs::config_dir().ok_or(derive_config::ConfigError::None)?;
36                let name = env!("CARGO_PKG_NAME");
37                let file = format!("{}.{}", name, #ext_name);
38
39                Ok(path.join(file))
40            }
41        }
42    } else if cfg!(feature = "etcetera") {
43        quote! {
44            fn path() -> std::result::Result<std::path::PathBuf, derive_config::ConfigError> {
45                use derive_config::etcetera::BaseStrategy;
46                let strategy = derive_config::etcetera::choose_base_strategy()?;
47                let path = strategy.config_dir();
48                let name = env!("CARGO_PKG_NAME");
49                let file = format!("{}.{}", name, #ext_name);
50
51                Ok(path.join(file))
52            }
53        }
54    } else {
55        quote! {
56            fn path() -> std::result::Result<std::path::PathBuf, derive_config::ConfigError> {
57                let mut path = std::env::current_exe()?;
58                path.set_file_name(env!("CARGO_CRATE_NAME"));
59                path.set_extension(#ext_name);
60
61                Ok(path)
62            }
63        }
64    };
65
66    quote! {
67        impl #trait_ident for #struct_name {
68            #path_method
69
70            fn save(&self) -> std::result::Result<(), derive_config::ConfigError> {
71                use std::io::Write;
72
73                let path = Self::path()?;
74                let mut file = std::fs::File::options()
75                    .write(true)
76                    .create(true)
77                    .truncate(true)
78                    .open(path)?;
79
80                let content = derive_config::#format_mod::#to_string_fn(&self)?;
81                file.write_all(content.as_bytes())?;
82
83                Ok(())
84            }
85
86            fn and_save(self) -> std::result::Result<Self, derive_config::ConfigError> {
87                self.save()?;
88                Ok(self)
89            }
90
91            fn load() -> std::result::Result<Self, derive_config::ConfigError> {
92                use std::io::{Read, Seek};
93
94                let path = Self::path()?;
95                let mut file = std::fs::File::open(&path)?;
96                let mut text = String::new();
97                file.read_to_string(&mut text)?;
98                file.rewind()?;
99
100                let config = derive_config::#format_mod::from_str(&text)?;
101
102                Ok(config)
103            }
104        }
105    }
106}
107
108fn derive_config(input: TokenStream, trait_name: &str, ext_name: &str) -> TokenStream {
109    let input = parse_macro_input!(input as DeriveInput);
110    let struct_name = input.ident;
111
112    let expanded = generate_impl(&struct_name, trait_name, ext_name);
113
114    TokenStream::from(expanded)
115}
116
117#[cfg(feature = "json")]
118#[proc_macro_derive(DeriveJsonConfig)]
119pub fn derive_json_config(input: TokenStream) -> TokenStream {
120    derive_config(input, "DeriveJsonConfig", "json")
121}
122
123#[cfg(feature = "toml")]
124#[proc_macro_derive(DeriveTomlConfig)]
125pub fn derive_toml_config(input: TokenStream) -> TokenStream {
126    derive_config(input, "DeriveTomlConfig", "toml")
127}
128
129#[cfg(feature = "yaml")]
130#[proc_macro_derive(DeriveYamlConfig)]
131pub fn derive_yaml_config(input: TokenStream) -> TokenStream {
132    derive_config(input, "DeriveYamlConfig", "yaml")
133}