extern crate proc_macro;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
braced,
parse::{Parse, ParseStream, Result},
parse_macro_input,
punctuated::Punctuated,
token, Attribute, Field, Ident, Lit, Meta, MetaNameValue, Token,
Visibility,
};
struct NamedFieldsStruct {
#[allow(dead_code)]
attrs: Vec<Attribute>,
#[allow(dead_code)]
vis: Visibility,
#[allow(dead_code)]
struct_token: Token![struct],
ident: Ident,
#[allow(dead_code)]
brace_token: token::Brace,
fields: Punctuated<Field, Token![,]>,
}
impl Parse for NamedFieldsStruct {
fn parse(input: ParseStream) -> Result<Self> {
let content;
#[allow(clippy::eval_order_dependence)]
Ok(Self {
attrs: input.call(Attribute::parse_outer)?,
vis: input.parse()?,
struct_token: input.parse()?,
ident: input.parse()?,
brace_token: braced!(content in input),
fields: content.parse_terminated(Field::parse_named)?,
})
}
}
fn gen_metric_builder(field: &Field) -> TokenStream {
let ty = &field.ty;
let name = field
.ident
.as_ref()
.expect("these should be guaranteed to be named already");
let name_str = name.to_string();
let help = field
.attrs
.iter()
.filter(|attr| attr.path.is_ident("doc"))
.filter_map(|attr| attr.parse_meta().ok())
.filter_map(|meta| {
if let Meta::NameValue(MetaNameValue {
lit: Lit::Str(s),
..
}) = meta
{ Some(s) } else { None }
})
.map(|help| help.value().trim_start().to_owned())
.next()
.map_or_else(
TokenStream::new,
|help| quote! {
.help(#help)
},
);
quote! {
#name: <#ty>::new(#name_str)
.unwrap()
#help,
}
}
fn gen_output_generator(field: &Field) -> TokenStream {
let ty = &field.ty;
let name = field
.ident
.as_ref()
.expect("these should be guaranteed to be named already");
quote! {
<#ty as ::hesione::Metric<_>>::
to_prometheus_lines(&self.#name, &mut buf, order);
}
}
#[proc_macro_derive(Hesione)]
pub fn main(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let named_fields_struct = parse_macro_input!(input as NamedFieldsStruct);
let struct_name = named_fields_struct.ident;
let metric_builders: TokenStream = named_fields_struct
.fields
.iter()
.flat_map(gen_metric_builder)
.collect();
let output_generators: TokenStream = named_fields_struct
.fields
.iter()
.flat_map(gen_output_generator)
.collect();
let default_impl = quote! {
impl Default for #struct_name {
fn default() -> Self {
Self {
#metric_builders
}
}
}
};
let metrics_impl = quote! {
impl ::hesione::Hesione for #struct_name {
fn to_prometheus_lines(
&self,
order: ::core::sync::atomic::Ordering,
capacity: Option<usize>
) -> String {
let mut buf = String::with_capacity(capacity.unwrap_or(2048));
#output_generators
buf
}
}
};
let tokens = quote! {
#metrics_impl
#default_impl
};
tokens.into()
}