libobs_source_macro/
lib.rs1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{
5 parse_macro_input, punctuated::Punctuated, Attribute, Data, DeriveInput, Expr, Fields, LitStr,
6 MetaNameValue, Token,
7};
8
9#[allow(unused_assignments)]
10#[proc_macro_attribute]
11pub fn obs_object_builder(attr: TokenStream, item: TokenStream) -> TokenStream {
73 let id = parse_macro_input!(attr as LitStr);
74
75 let input = parse_macro_input!(item as DeriveInput);
76
77 let name = input.ident;
78 let generics = input.generics;
79 let visibility = input.vis;
80 let attributes = input.attrs;
81
82 let fields = match input.data {
83 Data::Struct(data) => match data.fields {
84 Fields::Named(fields) => fields.named,
85 _ => panic!("Only named fields are supported"),
86 },
87 _ => panic!("Only structs are supported"),
88 };
89
90 let id_value = id.value();
91 let fields_tokens = fields.iter().map(|f| {
92 let name = &f.ident;
93 quote! {
94 #name: u8
96 }
97 });
98
99 let field_initializers = fields.iter().map(|f| {
100 let name = &f.ident;
101 quote! {
102 #name: 0
103 }
104 });
105
106 let obs_properties = fields
107 .iter()
108 .filter_map(|f| {
109 let attr = f.attrs.iter().find(|e| e.path().is_ident("obs_property"));
110
111 attr.map(|a| (f, a))
112 })
113 .collect::<Vec<_>>();
114
115 let mut functions = Vec::new();
116 for (field, attr) in obs_properties {
117 let field_type = &field.ty;
118 let field_name = field.ident.as_ref().unwrap();
119
120 let name_values: Punctuated<MetaNameValue, Token![,]> = attr
121 .parse_args_with(Punctuated::parse_terminated)
122 .expect(&format!(
123 "Field {} has invalid obs_property, should be name value",
124 field_name
125 ));
126
127 let type_t = &name_values
128 .iter()
129 .find(|e| e.path.get_ident().unwrap().to_string() == "type_t")
130 .expect("type_t is required for obs_property")
131 .value;
132
133 let type_t = match type_t {
134 syn::Expr::Lit(e) => match &e.lit {
135 syn::Lit::Str(s) => s.value(),
136 _ => panic!("type_t must be a string"),
137 },
138 _ => panic!("type_t must be a string"),
139 };
140
141 #[allow(unused_variables)]
142 let mut obs_settings_name = field_name.to_string();
143 let pot_name = &name_values
144 .iter()
145 .find(|e| e.path.get_ident().unwrap().to_string() == "settings_key");
146
147 if let Some(n) = pot_name {
148 obs_settings_name = match &n.value {
149 syn::Expr::Lit(e) => match &e.lit {
150 syn::Lit::Str(s) => s.value(),
151 _ => panic!("setings_key must be a string"),
152 },
153 _ => panic!("settings_key must be a string"),
154 };
155 }
156
157 let (_docs_str, docs_attr) = collect_doc(&field.attrs);
158
159 let obs_settings_key = LitStr::new(&obs_settings_name, Span::call_site());
160 let set_field = quote::format_ident!("set_{}", field_name);
161 let type_t_str = type_t.as_str();
162 let to_add = match type_t_str {
163 "enum" => {
164 quote! {
165 #(#docs_attr)*
166 pub fn #set_field(mut self, #field_name: #field_type) -> Self {
167 use num_traits::ToPrimitive;
168 use libobs_wrapper::data::ObsObjectBuilder;
169 let val = #field_name.to_i32().unwrap();
170
171 self.get_or_create_settings()
172 .set_int(#obs_settings_key, val as i64);
173
174 self
175 }
176 }
177 }
178 "enum_string" => {
179 quote! {
180 #(#docs_attr)*
181 pub fn #set_field(mut self, #field_name: #field_type) -> Self {
182 use libobs_wrapper::data::{ObsObjectBuilder, StringEnum};
183
184 self.get_or_create_settings()
185 .set_string(#obs_settings_key, #field_name.to_str());
186
187 self
188 }
189 }
190 }
191 "string" => {
192 quote! {
193 #(#docs_attr)*
194 pub fn #set_field(mut self, #field_name: impl Into<libobs_wrapper::utils::ObsString>) -> Self {
195 use libobs_wrapper::data::ObsObjectBuilder;
196 self.get_or_create_settings()
197 .set_string(#obs_settings_key, #field_name);
198 self
199 }
200 }
201 }
202 "bool" => {
203 quote! {
204 #(#docs_attr)*
205 pub fn #set_field(mut self, #field_name: bool) -> Self {
206 use libobs_wrapper::data::ObsObjectBuilder;
207 self.get_or_create_settings()
208 .set_bool(#obs_settings_key, #field_name);
209 self
210 }
211 }
212 }
213 "int" => {
214 quote! {
215 #(#docs_attr)*
216 pub fn #set_field(mut self, #field_name: i64) -> Self {
217 use libobs_wrapper::data::ObsObjectBuilder;
218 self.get_or_create_settings()
219 .set_int(#obs_settings_key, #field_name);
220 self
221 }
222 }
223 }
224 _ => panic!(
225 "Unsupported type_t {}. Should either be `enum`, `string`, `bool` or `int`",
226 type_t
227 ),
228 };
229
230 functions.push(to_add);
231 }
232
233 let expanded = quote! {
234 #(#attributes)*
235 #[allow(dead_code)]
236 #visibility struct #name #generics {
237 #(#fields_tokens,)*
238 settings: Option<libobs_wrapper::data::ObsData>,
239 hotkeys: Option<libobs_wrapper::data::ObsData>,
240 name: libobs_wrapper::utils::ObsString
241 }
242
243 impl libobs_wrapper::data::ObsObjectBuilder for #name {
244 fn new(name: impl Into<libobs_wrapper::utils::ObsString>) -> Self {
245 Self {
246 #(#field_initializers,)*
247 settings: None,
248 hotkeys: None,
249 name: name.into(),
250 }
251 }
252
253 fn get_settings(&self) -> &Option<libobs_wrapper::data::ObsData> {
254 &self.settings
255 }
256
257 fn get_settings_mut(&mut self) -> &mut Option<libobs_wrapper::data::ObsData> {
258 &mut self.settings
259 }
260
261 fn get_hotkeys(&self) -> &Option<libobs_wrapper::data::ObsData> {
262 &self.hotkeys
263 }
264
265 fn get_hotkeys_mut(&mut self) -> &mut Option<libobs_wrapper::data::ObsData> {
266 &mut self.hotkeys
267 }
268
269 fn get_name(&self) -> libobs_wrapper::utils::ObsString {
270 self.name.clone()
271 }
272
273 fn get_id() -> libobs_wrapper::utils::ObsString {
274 #id_value.into()
275 }
276 }
277
278 impl #name {
279 #(#functions)*
280 }
281 };
282
283 TokenStream::from(expanded)
284}
285
286fn collect_doc(attrs: &Vec<Attribute>) -> (Vec<String>, Vec<&Attribute>) {
287 let mut docs_str = Vec::new();
288 let mut docs_attr = Vec::new();
289 for attr in attrs {
290 let name_val = match &attr.meta {
291 syn::Meta::NameValue(n) => n,
292 _ => continue,
293 };
294
295 let is_doc = name_val.path.is_ident("doc");
296 if !is_doc {
297 continue;
298 }
299
300 let lit = match &name_val.value {
301 Expr::Lit(l) => match &l.lit {
302 syn::Lit::Str(s) => s.value(),
303 _ => continue,
304 },
305 _ => continue,
306 };
307
308 docs_str.push(lit);
309 docs_attr.push(attr);
310 }
311
312 (docs_str, docs_attr)
313}