include_mdtests/
lib.rs

1//! See [`include_mdtests!`] macro documentation.
2use proc_macro2::Span;
3use quote::quote;
4use syn::{Ident, LitStr, parse_macro_input};
5
6#[doc = include_str!("../README.md")]
7#[proc_macro]
8pub fn include_mdtests(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9    let current_dir = std::env::current_dir().unwrap();
10    let input_glob = parse_macro_input!(input as LitStr);
11
12    let doc_mods = glob::glob(input_glob.value().as_str())
13        .expect("Failed to read glob pattern")
14        .map(|entry| entry.expect("Failed to read glob entry"))
15        .map(|path| {
16            let path_lit_str = {
17                let path_abs = current_dir.join(path.clone());
18                let path_abs_str = path_abs.to_str().expect("Failed to convert path to string");
19                LitStr::new(path_abs_str, Span::call_site())
20            };
21
22            let mod_ident = {
23                let mut ident_string = path
24                    .to_string_lossy()
25                    .chars()
26                    .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
27                    .collect::<String>();
28                if ident_string
29                    .chars()
30                    .next()
31                    .is_none_or(|c| c.is_ascii_digit())
32                {
33                    // Identifiers cannot start with a digit, prepend an underscore.
34                    ident_string.insert(0, '_');
35                }
36                Ident::new(&ident_string, Span::call_site())
37            };
38
39            quote! {
40                #[doc = include_str!(#path_lit_str)]
41                mod #mod_ident {}
42            }
43        });
44
45    let out = quote! {
46        #( #doc_mods )*
47    };
48    out.into()
49}