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