1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use regex::{Captures, Regex};
4use syn::{parse_macro_input, DeriveInput};
5
6fn to_snake_case(value: String) -> String {
7 let re1 = Regex::new(r"([A-Z]+)([A-Z][a-z])").unwrap();
8 let re2 = Regex::new(r"([a-z]|\d)([A-Z])").unwrap();
9
10 let replaced1 = re1.replace_all(&value, |caps: &Captures| {
11 format!("{}_{}", &caps[1], &caps[2])
12 });
13
14 let replaced2 = re2.replace_all(&replaced1, |caps: &Captures| {
15 format!("{}_{}", &caps[1], &caps[2])
16 });
17
18 replaced2.to_lowercase()
19}
20
21#[proc_macro_derive(Language)]
22pub fn derive_language(input: TokenStream) -> TokenStream {
23 let input = parse_macro_input!(input as DeriveInput);
24 let language = input.ident;
25 let language_name = format_ident!("{}", to_snake_case(language.to_string()));
26
27 let expanded = quote! {
28 impl Language for #language {
29 fn name(&self) -> &str {
30 stringify!(#language_name)
31 }
32 }
33 };
34
35 TokenStream::from(expanded)
36}
37
38#[proc_macro_derive(TestFrameworkMeta)]
39pub fn derive_test_framework_meta(input: TokenStream) -> TokenStream {
40 let input = parse_macro_input!(input as DeriveInput);
41 let test_framework = input.ident;
42 let test_framework_name = format_ident!("{}", to_snake_case(test_framework.to_string()));
43
44 let expanded = quote! {
45 impl TestFrameworkMeta for #test_framework {
46 fn language(&self) -> &dyn crate::language::Language {
47 &self.language
48 }
49
50 fn name(&self) -> &str {
51 stringify!(#test_framework_name)
52 }
53
54 fn pattern(&self) -> Result<regex::Regex, regex::Error> {
55 regex::Regex::new(&self.pattern)
56 }
57
58 fn default_executable(&self) -> Option<crate::ArgsList> {
59 if self.executable.is_empty() {
60 None
61 } else {
62 Some(self.executable.clone().into_iter().map(|s| s.to_string()).collect())
63 }
64 }
65
66 fn args(&self) -> crate::ArgsList {
67 self.args.clone().into_iter().map(|s| s.to_string()).collect()
68 }
69
70
71 fn test_pattern(&self) -> &str {
72 &self.test_pattern
73 }
74
75 fn namespace_pattern(&self) -> &str {
76 &self.namespace_pattern
77 }
78 }
79 };
80
81 TokenStream::from(expanded)
82}