use crate::HandlerSignature;
use std::path::Path;
pub fn extract_handler_names_from_file(rs_file_path: &Path) -> Vec<String> {
let content = match std::fs::read_to_string(rs_file_path) {
Ok(content) => content,
Err(_) => return vec![],
};
extract_handler_names_from_source(&content)
}
fn extract_handler_names_from_source(source: &str) -> Vec<String> {
let syntax = match syn::parse_file(source) {
Ok(syntax) => syntax,
Err(_) => return vec![],
};
for item in syntax.items {
if let syn::Item::Macro(mac) = item {
let path = &mac.mac.path;
if path.segments.last().map(|s| s.ident.to_string())
== Some("inventory_handlers".to_string())
{
return parse_handler_list_from_tokens(&mac.mac.tokens);
}
}
}
vec![]
}
fn parse_handler_list_from_tokens(tokens: &proc_macro2::TokenStream) -> Vec<String> {
let mut handlers = Vec::new();
let tokens_str = tokens.to_string();
for part in tokens_str.split(',') {
let name = part.trim().to_string();
if !name.is_empty() {
handlers.push(name);
}
}
handlers
}
pub fn extract_handler_signatures_from_file(rs_file_path: &Path) -> Vec<HandlerSignature> {
let content = match std::fs::read_to_string(rs_file_path) {
Ok(content) => content,
Err(_) => return vec![],
};
let handler_names = extract_handler_names_from_source(&content);
let syntax = match syn::parse_file(&content) {
Ok(syntax) => syntax,
Err(_) => {
return handler_names
.into_iter()
.map(|name| HandlerSignature {
name,
param_type: None,
returns_command: false,
})
.collect();
}
};
handler_names
.into_iter()
.map(|name| {
if let Some(signature) = find_handler_function_signature(&syntax, &name) {
signature
} else {
HandlerSignature {
name,
param_type: None,
returns_command: false,
}
}
})
.collect()
}
fn find_handler_function_signature(
syntax: &syn::File,
handler_name: &str,
) -> Option<HandlerSignature> {
use syn::{FnArg, Item, ReturnType};
for item in &syntax.items {
if let Item::Fn(func) = item {
if func.sig.ident == handler_name {
let has_ui_handler_attr = func.attrs.iter().any(|attr| {
attr.path().segments.last().map(|s| s.ident.to_string())
== Some("ui_handler".to_string())
});
if !has_ui_handler_attr {
continue;
}
let mut param_type: Option<String> = None;
let mut param_count = 0;
for input in &func.sig.inputs {
if let FnArg::Typed(pat_type) = input {
param_count += 1;
if param_count > 1 {
let ty = &pat_type.ty;
let type_str = quote::quote!(#ty).to_string();
param_type = Some(type_str.replace(" ", ""));
}
}
}
let returns_command = if let ReturnType::Type(_, ty) = &func.sig.output {
let return_str = quote::quote!(#ty).to_string();
return_str.contains("Command")
} else {
false
};
return Some(HandlerSignature {
name: handler_name.to_string(),
param_type,
returns_command,
});
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_handler_names() {
let source = r#"
use dampen_macros::{ui_handler, inventory_handlers};
#[ui_handler]
fn increment(model: &mut Model) {
model.count += 1;
}
#[ui_handler]
fn decrement(model: &mut Model) {
model.count -= 1;
}
inventory_handlers! {
increment,
decrement
}
"#;
let handlers = extract_handler_names_from_source(source);
assert_eq!(handlers, vec!["increment", "decrement"]);
}
#[test]
fn test_extract_empty_inventory() {
let source = r#"
use dampen_macros::ui_handler;
#[ui_handler]
fn my_handler(model: &mut Model) {}
"#;
let handlers = extract_handler_names_from_source(source);
assert!(handlers.is_empty());
}
#[test]
fn test_extract_single_handler() {
let source = r#"
inventory_handlers! { greet }
"#;
let handlers = extract_handler_names_from_source(source);
assert_eq!(handlers, vec!["greet"]);
}
}