cosmic_config_derive/
lib.rs
1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{self};
4
5#[proc_macro_derive(CosmicConfigEntry, attributes(version, id))]
6pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
7 let ast = syn::parse(input).unwrap();
10
11 impl_cosmic_config_entry_macro(&ast)
13}
14
15fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
16 let attributes = &ast.attrs;
17 let version = attributes
18 .iter()
19 .find_map(|attr| {
20 if attr.path.is_ident("version") {
21 match attr.parse_meta() {
22 Ok(syn::Meta::NameValue(syn::MetaNameValue {
23 lit: syn::Lit::Int(lit_int),
24 ..
25 })) => Some(lit_int.base10_parse::<u64>().unwrap()),
26 _ => None,
27 }
28 } else {
29 None
30 }
31 })
32 .unwrap_or(0);
33
34 let name = &ast.ident;
35
36 let fields = match ast.data {
38 syn::Data::Struct(ref data_struct) => match data_struct.fields {
39 syn::Fields::Named(ref fields) => &fields.named,
40 _ => unimplemented!("Only named fields are supported"),
41 },
42 _ => unimplemented!("Only structs are supported"),
43 };
44
45 let write_each_config_field = fields.iter().map(|field| {
46 let field_name = &field.ident;
47 quote! {
48 cosmic_config::ConfigSet::set(&tx, stringify!(#field_name), &self.#field_name)?;
49 }
50 });
51
52 let get_each_config_field = fields.iter().map(|field| {
53 let field_name = &field.ident;
54 let field_type = &field.ty;
55 quote! {
56 match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
57 Ok(#field_name) => default.#field_name = #field_name,
58 Err(why) if matches!(why, cosmic_config::Error::NoConfigDirectory) => (),
59 Err(e) => errors.push(e),
60 }
61 }
62 });
63
64 let update_each_config_field = fields.iter().map(|field| {
65 let field_name = &field.ident;
66 let field_type = &field.ty;
67 quote! {
68 stringify!(#field_name) => {
69 match cosmic_config::ConfigGet::get::<#field_type>(config, stringify!(#field_name)) {
70 Ok(value) => {
71 if self.#field_name != value {
72 keys.push(stringify!(#field_name));
73 }
74 self.#field_name = value;
75 },
76 Err(e) => {
77 errors.push(e);
78 }
79 }
80 }
81 }
82 });
83
84 let setters = fields.iter().filter_map(|field| {
85 let field_name = &field.ident.as_ref()?;
86 let field_type = &field.ty;
87 let setter_name = quote::format_ident!("set_{}", field_name);
88 let doc = format!("Sets [`{name}::{field_name}`] and writes to [`cosmic_config::Config`] if changed");
89 Some(quote! {
90 #[doc = #doc]
91 pub fn #setter_name(&mut self, config: &cosmic_config::Config, value: #field_type) -> Result<bool, cosmic_config::Error> {
94 if self.#field_name != value {
95 self.#field_name = value;
96 cosmic_config::ConfigSet::set(config, stringify!(#field_name), &self.#field_name)?;
97 Ok(true)
98 } else {
99 Ok(false)
100 }
101 }
102 })
103 });
104
105 let gen = quote! {
106 impl CosmicConfigEntry for #name {
107 const VERSION: u64 = #version;
108
109 fn write_entry(&self, config: &cosmic_config::Config) -> Result<(), cosmic_config::Error> {
110 let tx = config.transaction();
111 #(#write_each_config_field)*
112 tx.commit()
113 }
114
115 fn get_entry(config: &cosmic_config::Config) -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
116 let mut default = Self::default();
117 let mut errors = Vec::new();
118
119 #(#get_each_config_field)*
120
121 if errors.is_empty() {
122 Ok(default)
123 } else {
124 Err((errors, default))
125 }
126 }
127
128 fn update_keys<T: AsRef<str>>(&mut self, config: &cosmic_config::Config, changed_keys: &[T]) -> (Vec<cosmic_config::Error>, Vec<&'static str>){
129 let mut keys = Vec::with_capacity(changed_keys.len());
130 let mut errors = Vec::new();
131 for key in changed_keys.iter() {
132 match key.as_ref() {
133 #(#update_each_config_field)*
134 _ => (),
135 }
136 }
137 (errors, keys)
138 }
139 }
140
141 impl #name {
142 #(#setters)*
143 }
144 };
145
146 gen.into()
147}