#![no_std]
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, parse_quote, DeriveInput, FnArg, Ident, ItemFn, Pat, PatIdent, PatType,
Path, Type, TypePath,
};
#[proc_macro_derive(DotEnvDefault)]
pub fn derive_dotenv_parser(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let output = quote! {
impl entrypoint::DotEnvParserConfig for #name {}
};
TokenStream::from(output)
}
#[proc_macro_derive(LoggerDefault, attributes(log_format, log_level, log_writer))]
pub fn derive_logger(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let mut log_format: syn::ExprCall = parse_quote! { clone() };
let mut log_level: syn::ExprPath =
parse_quote! { tracing_subscriber::fmt::Subscriber::DEFAULT_MAX_LEVEL };
let mut log_writer: syn::ExprPath = parse_quote! { std::io::stdout };
for attr in input.attrs {
if attr.path().is_ident("log_format") {
let key: syn::ExprPath = attr
.parse_args()
.expect("required log_format input parameter is missing or malformed");
log_format = if key.path.is_ident("compact") {
parse_quote! { compact() }
} else if key.path.is_ident("default") || key.path.is_ident("full") {
parse_quote! { clone() }
} else if key.path.is_ident("json") {
parse_quote! { json() }
} else if key.path.is_ident("pretty") {
parse_quote! { pretty() }
} else {
panic!(
"log_format input parameter is unknown type: {:?}",
key.path.get_ident()
);
};
} else if attr.path().is_ident("log_level") {
log_level = attr
.parse_args()
.expect("required log_level input parameter is missing or malformed");
} else if attr.path().is_ident("log_writer") {
log_writer = attr
.parse_args()
.expect("required log_writer input parameter is missing or malformed");
}
}
let output = quote! {
impl entrypoint::LoggerConfig for #name {
fn default_log_format<S, N>(&self) -> impl FormatEvent<S, N> + Send + Sync + 'static
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'writer> FormatFields<'writer> + 'static,
{
Format::default().#log_format
}
fn default_log_level(&self) -> entrypoint::tracing_subscriber::filter::LevelFilter {
#log_level
}
fn default_log_writer(&self) -> impl for<'writer> MakeWriter<'writer> + Send + Sync + 'static {
#log_writer
}
}
};
TokenStream::from(output)
}
#[proc_macro_attribute]
pub fn entrypoint(_args: TokenStream, item: TokenStream) -> TokenStream {
let tokens = parse_macro_input!(item as ItemFn);
let attrs = { tokens.attrs };
let (input_param_ident, input_param_type) = {
let mut input_param_ident: Option<Ident> = None;
let mut input_param_type: Option<Path> = None;
for input in tokens.sig.inputs.clone() {
if let FnArg::Typed(PatType {
pat: name,
ty: r#type,
..
}) = input
{
match (*name, *r#type) {
(
Pat::Ident(PatIdent { ident: name, .. }),
Type::Path(TypePath { path: r#type, .. }),
) => {
input_param_ident = Some(name);
input_param_type = Some(r#type.clone());
}
(_, _) => {
continue;
}
}
}
}
(
input_param_ident.expect("required entrypoint input parameter is missing or malformed"),
input_param_type.expect("required entrypoint input parameter is missing or malformed"),
)
};
let signature = {
let mut signature = tokens.sig.clone();
signature.ident = format_ident!("main");
signature.inputs.clear();
signature.output = parse_quote! {-> entrypoint::anyhow::Result<()>};
signature
};
let block = { tokens.block };
quote! {
#(#attrs)*
#signature {
<#input_param_type as entrypoint::clap::Parser>::parse().entrypoint(|#input_param_ident| { #block })
}
}
.into()
}