diff options
Diffstat (limited to 'macros/cmd_system_macros')
| -rw-r--r-- | macros/cmd_system_macros/Cargo.toml | 12 | ||||
| -rw-r--r-- | macros/cmd_system_macros/src/lib.rs | 92 |
2 files changed, 104 insertions, 0 deletions
diff --git a/macros/cmd_system_macros/Cargo.toml b/macros/cmd_system_macros/Cargo.toml new file mode 100644 index 0000000..4a91064 --- /dev/null +++ b/macros/cmd_system_macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cmd_system_macros" +version.workspace = true +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits", "visit"] } diff --git a/macros/cmd_system_macros/src/lib.rs b/macros/cmd_system_macros/src/lib.rs new file mode 100644 index 0000000..e585782 --- /dev/null +++ b/macros/cmd_system_macros/src/lib.rs @@ -0,0 +1,92 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Expr, ItemFn, Lit, Type, parse_macro_input, parse_quote}; + +#[proc_macro_attribute] +pub fn exec(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(item as ItemFn); + let fn_block = &input_fn.block; + + let mut output_mappings = Vec::new(); + extract_cmd_output_macros(fn_block, &mut output_mappings); + + let mapping_fn = generate_mapping_function(&output_mappings); + + let expanded = quote! { + #input_fn + + #mapping_fn + }; + + TokenStream::from(expanded) +} + +fn extract_cmd_output_macros(block: &syn::Block, mappings: &mut Vec<(String, syn::Type)>) { + use syn::visit::Visit; + + struct CmdOutputVisitor<'a> { + mappings: &'a mut Vec<(String, syn::Type)>, + } + + impl<'a> syn::visit::Visit<'a> for CmdOutputVisitor<'a> { + fn visit_macro(&mut self, mac: &'a syn::Macro) { + if mac.path.is_ident("cmd_output") { + let nested_result = syn::parse2::<syn::ExprTuple>(mac.tokens.clone()); + if let Ok(nested) = nested_result { + if nested.elems.len() < 2 { + syn::visit::visit_macro(self, mac); + return; + } + + let first_elem = &nested.elems[0]; + let second_elem = &nested.elems[1]; + + let type_path_opt = match first_elem { + Expr::Path(path) => Some(path), + _ => None, + }; + + let lit_str_opt = match second_elem { + Expr::Lit(lit) => match &lit.lit { + Lit::Str(lit_str) => Some(lit_str), + _ => None, + }, + _ => None, + }; + + if let (Some(type_path), Some(lit_str)) = (type_path_opt, lit_str_opt) { + let type_name = lit_str.value(); + if let Some(type_ident) = type_path.path.get_ident() { + let ty: Type = parse_quote!(#type_ident); + self.mappings.push((type_name, ty)); + } + } + } + } + + syn::visit::visit_macro(self, mac); + } + } + + let mut visitor = CmdOutputVisitor { mappings }; + visitor.visit_block(block); +} + +fn generate_mapping_function(mappings: &[(String, syn::Type)]) -> proc_macro2::TokenStream { + let mapping_entries: Vec<_> = mappings + .iter() + .map(|(name, ty)| { + quote! { + map.insert(#name.to_string(), std::any::TypeId::of::<#ty>()); + } + }) + .collect(); + + quote! { + fn get_output_type_mapping() -> std::collections::HashMap<String, std::any::TypeId> { + let mut map = std::collections::HashMap::new(); + #(#mapping_entries)* + map + } + } +} |
