#![deny(unused_crate_dependencies)]
#![deny(missing_docs)]
#![deny(clippy::dbg_macro)]
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{parse2, parse_quote, punctuated::Punctuated, Result, Token};
mod types;
use self::types::*;
#[cfg(test)]
mod tests;
#[proc_macro]
pub fn error(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
gum(item, Level::Error)
}
#[proc_macro]
pub fn warn(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
gum(item, Level::Warn)
}
#[proc_macro]
pub fn warn_if_frequent(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ArgsIfFrequent { freq, max_rate, rest } = parse2(item.into()).unwrap();
let freq_expr = freq.expr;
let max_rate_expr = max_rate.expr;
let debug: proc_macro2::TokenStream = gum(rest.clone().into(), Level::Debug).into();
let warn: proc_macro2::TokenStream = gum(rest.into(), Level::Warn).into();
let stream = quote! {
if #freq_expr .is_frequent(#max_rate_expr) {
#warn
} else {
#debug
}
};
stream.into()
}
#[proc_macro]
pub fn info(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
gum(item, Level::Info)
}
#[proc_macro]
pub fn debug(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
gum(item, Level::Debug)
}
#[proc_macro]
pub fn trace(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
gum(item, Level::Trace)
}
pub(crate) fn gum(item: proc_macro::TokenStream, level: Level) -> proc_macro::TokenStream {
let item: TokenStream = item.into();
let res = expander::Expander::new("gum")
.add_comment("Generated overseer code by `gum::warn!(..)`".to_owned())
.dry(true)
.verbose(false)
.fmt(expander::Edition::_2021)
.maybe_write_to_out_dir(impl_gum2(item, level))
.expect("Expander does not fail due to IO in OUT_DIR. qed");
res.unwrap_or_else(|err| err.to_compile_error()).into()
}
pub(crate) fn impl_gum2(orig: TokenStream, level: Level) -> Result<TokenStream> {
let args: Args = parse2(orig)?;
let krate = support_crate();
let span = Span::call_site();
let Args { target, comma, mut values, fmt } = args;
let maybe_candidate_hash = values.iter_mut().find(|value| value.as_ident() == "candidate_hash");
if let Some(kv) = maybe_candidate_hash {
let (ident, rhs_expr, replace_with) = match kv {
Value::Alias(alias) => {
let ValueWithAliasIdent { alias, marker, expr, .. } = alias.clone();
(
alias.clone(),
expr.to_token_stream(),
Some(Value::Value(ValueWithFormatMarker {
marker,
ident: alias,
dot: None,
inner: Punctuated::new(),
})),
)
},
Value::Value(value) => (value.ident.clone(), value.ident.to_token_stream(), None),
};
if let Some(replace_with) = replace_with {
let _old = std::mem::replace(kv, replace_with);
};
let had_trailing_comma = values.trailing_punct();
if !had_trailing_comma {
values.push_punct(Token![,](span));
}
values.push_value(parse_quote! {
traceID = % trace_id
});
if had_trailing_comma {
values.push_punct(Token![,](span));
}
Ok(quote! {
if #krate :: enabled!(#target #comma #level) {
use ::std::ops::Deref;
let value = #rhs_expr;
let value = &value;
let value: & #krate:: Hash = value.deref();
let #ident: #krate:: Hash = * value;
let trace_id = #krate:: hash_to_trace_identifier ( #ident );
#krate :: event!(
#target #comma #level, #values #fmt
)
}
})
} else {
Ok(quote! {
#krate :: event!(
#target #comma #level, #values #fmt
)
})
}
}
fn support_crate() -> TokenStream {
let support_crate_name = if cfg!(test) {
quote! {crate}
} else {
use proc_macro_crate::{crate_name, FoundCrate};
let crate_name = crate_name("tracing-gum")
.expect("Support crate `tracing-gum` is present in `Cargo.toml`. qed");
match crate_name {
FoundCrate::Itself => quote! {crate},
FoundCrate::Name(name) => Ident::new(&name, Span::call_site()).to_token_stream(),
}
};
support_crate_name
}