use proc_macro2::TokenStream as Ts2;
use quote::{format_ident, quote, quote_spanned};
use syn::Ident;
use crate::{MetricsField, MetricsFieldKind, NameStyle, RootAttributes, inflect::metric_name};
mod enum_impl;
mod struct_impl;
pub(crate) use enum_impl::generate_enum_entry_impl;
pub(crate) use struct_impl::generate_struct_entry_impl;
fn make_ns(ns: NameStyle, span: proc_macro2::Span) -> Ts2 {
match ns {
NameStyle::PascalCase => quote_spanned! {span=> NS::PascalCase },
NameStyle::SnakeCase => quote_spanned! {span=> NS::SnakeCase },
NameStyle::KebabCase => quote_spanned! {span=> NS::KebabCase },
NameStyle::Preserve => quote_spanned! {span=> NS },
}
}
fn const_str(ident: &syn::Ident, value: &str) -> Ts2 {
quote_spanned! {ident.span()=>
struct #ident;
impl ::metrique::concat::ConstStr for #ident {
const VAL: &'static str = #value;
}
}
}
fn make_inflect_base(
ns: &Ts2,
inflect_method: syn::Ident,
span: proc_macro2::Span,
mut name_fn: impl FnMut(NameStyle) -> String,
) -> (Ts2, Ts2) {
let preserve_val = name_fn(NameStyle::Preserve);
let kebab_val = name_fn(NameStyle::KebabCase);
let pascal_val = name_fn(NameStyle::PascalCase);
let snake_val = name_fn(NameStyle::SnakeCase);
let ident_base: String = NameStyle::PascalCase
.apply(&preserve_val)
.chars()
.filter(|c| c.is_alphanumeric())
.collect();
let name_ident = format_ident!(
"{}{}",
ident_base,
NameStyle::Preserve.to_word(),
span = span
);
let name_kebab = format_ident!(
"{}{}",
ident_base,
NameStyle::KebabCase.to_word(),
span = span
);
let name_pascal = format_ident!(
"{}{}",
ident_base,
NameStyle::PascalCase.to_word(),
span = span
);
let name_snake = format_ident!(
"{}{}",
ident_base,
NameStyle::SnakeCase.to_word(),
span = span
);
let extra_preserve = const_str(&name_ident, &preserve_val);
let extra_kebab = const_str(&name_kebab, &kebab_val);
let extra_pascal = const_str(&name_pascal, &pascal_val);
let extra_snake = const_str(&name_snake, &snake_val);
let extra = quote!(
#extra_preserve
#extra_kebab
#extra_pascal
#extra_snake
);
let inflected_type = quote!(
<#ns as ::metrique::NameStyle>::#inflect_method<#name_ident, #name_pascal, #name_snake, #name_kebab>
);
(extra, inflected_type)
}
fn make_inflect(
ns: &Ts2,
span: proc_macro2::Span,
name_fn: impl FnMut(NameStyle) -> String,
) -> (Ts2, Ts2) {
make_inflect_base(ns, format_ident!("Inflect", span = span), span, name_fn)
}
fn make_inflect_affix(
ns: &Ts2,
span: proc_macro2::Span,
name_fn: impl FnMut(NameStyle) -> String,
) -> (Ts2, Ts2) {
make_inflect_base(
ns,
format_ident!("InflectAffix", span = span),
span,
name_fn,
)
}
pub(crate) fn make_inflect_prefix(ns: &Ts2, prefix: &str, span: proc_macro2::Span) -> (Ts2, Ts2) {
let (extra, inflected) = make_inflect_affix(ns, span, |style| style.apply_prefix(prefix));
let ns_with_prefix = quote!(
<#ns as ::metrique::NameStyle>::AppendPrefix<#inflected>
);
(extra, ns_with_prefix)
}
pub(crate) fn make_exact_prefix(
ns: &Ts2,
exact_prefix: &str,
span: proc_macro2::Span,
) -> (Ts2, Ts2) {
let pascal_val = NameStyle::PascalCase.apply(exact_prefix);
let ident_base: String = pascal_val.chars().filter(|c| c.is_alphanumeric()).collect();
let prefix_ident = format_ident!("{}Preserve", ident_base, span = span);
let extra = const_str(&prefix_ident, exact_prefix);
let ns_with_prefix = quote!(
<#ns as ::metrique::NameStyle>::AppendPrefix<#prefix_ident>
);
(extra, ns_with_prefix)
}
fn generate_field_writes(
fields: &[MetricsField],
root_attrs: &RootAttributes,
field_access: impl Fn(&Ts2) -> Ts2,
) -> Vec<Ts2> {
let mut writes = Vec::new();
for field in fields {
let field_span = field.span;
let ns = make_ns(root_attrs.rename_all, field_span);
match &field.attrs.kind {
MetricsFieldKind::Timestamp(span) => {
let field_access = field_access(&field.ident);
writes.push(quote_spanned! {*span=>
#[allow(clippy::useless_conversion)]
{
::metrique::writer::EntryWriter::timestamp(writer, (*#field_access).into());
}
});
}
MetricsFieldKind::FlattenEntry(span) => {
let field_access = field_access(&field.ident);
writes.push(quote_spanned! {*span=>
::metrique::writer::Entry::write(#field_access, writer);
});
}
MetricsFieldKind::Flatten { span, prefix } => {
let (extra, ns) = match prefix {
None => (quote!(), ns),
Some(prefix) => prefix.append_to(&ns, field_span),
};
let field_access = field_access(&field.ident);
writes.push(quote_spanned! {*span=>
#extra
::metrique::InflectableEntry::<#ns>::write(#field_access, writer);
});
}
MetricsFieldKind::Ignore(_) => {
continue;
}
MetricsFieldKind::Field { format, .. } => {
let (extra, name) = make_inflect_metric_name(root_attrs, field);
let field_access = field_access(&field.ident);
let value = crate::value_impl::format_value(format, field_span, field_access);
writes.push(quote_spanned! {field_span=>
::metrique::writer::EntryWriter::value(writer,
{
#extra
::metrique::concat::const_str_value::<#name>()
}
, #value);
});
}
}
}
writes
}
fn make_binary_tree_chain(iterators: Vec<Ts2>) -> Ts2 {
if iterators.is_empty() {
return quote! { ::std::iter::empty() };
}
if iterators.len() == 1 {
return iterators[0].clone();
}
let mid = iterators.len() / 2;
let left = make_binary_tree_chain(iterators[..mid].to_vec());
let right = make_binary_tree_chain(iterators[mid..].to_vec());
quote! { #left.chain(#right) }
}
fn make_inflect_metric_name(root_attrs: &RootAttributes, field: &MetricsField) -> (Ts2, Ts2) {
make_inflect(
&make_ns(root_attrs.rename_all, field.span),
field.span,
|style| metric_name(root_attrs, style, field),
)
}
fn collect_field_sample_group<'a>(
field: &'a MetricsField,
root_attrs: &RootAttributes,
field_access: impl FnOnce(&Ts2) -> Ts2,
) -> Option<(&'a Ts2, Ts2)> {
let field_ident = &field.ident;
match &field.attrs.kind {
MetricsFieldKind::Flatten { span, .. } => {
let ns = make_ns(root_attrs.rename_all, field.span);
let access = field_access(field_ident);
Some((
field_ident,
quote_spanned!(*span=>
::metrique::InflectableEntry::<#ns>::sample_group(#access)
),
))
}
MetricsFieldKind::FlattenEntry(span) => {
let access = field_access(field_ident);
Some((
field_ident,
quote_spanned!(*span=>
::metrique::writer::Entry::sample_group(#access)
),
))
}
MetricsFieldKind::Field {
sample_group: Some(span),
..
} => {
let (extra, name) = make_inflect_metric_name(root_attrs, field);
let access = field_access(field_ident);
Some((
field_ident,
quote_spanned!(*span=>
{
#extra
::std::iter::once((
::metrique::concat::const_str_value::<#name>(),
::metrique::writer::core::SampleGroup::as_sample_group(#access)
))
}
),
))
}
MetricsFieldKind::Field {
sample_group: None, ..
}
| MetricsFieldKind::Ignore(_)
| MetricsFieldKind::Timestamp(_) => None,
}
}