teloxide-macros 0.4.0

The teloxide's procedural macros
Documentation
extern crate quote;

use quote::{__private::Span, quote, ToTokens};
use syn::{FieldsNamed, FieldsUnnamed, Type};

#[derive(Debug)]
pub enum ParserType {
    Default,
    Split { separator: Option<String> },
    Custom(String),
}

impl ParserType {
    pub fn parse(data: &str) -> Self {
        match data {
            "default" => ParserType::Default,
            "split" => ParserType::Split { separator: None },
            s => ParserType::Custom(s.to_owned()),
        }
    }
}

pub fn impl_parse_args_unnamed(
    data: &FieldsUnnamed,
    variant: impl ToTokens,
    parser_type: &ParserType,
) -> quote::__private::TokenStream {
    let get_arguments = create_parser(
        parser_type,
        data.unnamed.iter().map(|f| &f.ty),
        data.unnamed.len(),
    );
    let iter = (0..data.unnamed.len()).map(syn::Index::from);
    let mut initialization = quote! {};
    for i in iter {
        initialization.extend(quote! { arguments.#i, })
    }
    let res = quote! {
        {
            #get_arguments
            #variant(#initialization)
        }
    };
    res
}

pub fn impl_parse_args_named(
    data: &FieldsNamed,
    variant: impl ToTokens,
    parser_type: &ParserType,
) -> quote::__private::TokenStream {
    let get_arguments = create_parser(
        parser_type,
        data.named.iter().map(|f| &f.ty),
        data.named.len(),
    );
    let i = (0..data.named.len()).map(syn::Index::from);
    let name = data.named.iter().map(|f| f.ident.as_ref().unwrap());
    let res = quote! {
        {
            #get_arguments
            #variant { #(#name: arguments.#i),* }
        }
    };
    res
}

fn create_parser<'a>(
    parser_type: &ParserType,
    mut types: impl Iterator<Item = &'a Type>,
    count_args: usize,
) -> quote::__private::TokenStream {
    let function_to_parse = match parser_type {
        ParserType::Default => match count_args {
            1 => {
                let ty = types.next().expect("count_args != types.len()");
                quote! { (|s: String| {
                    let res = <#ty>::from_str(&s)
                        .map_err(|e|ParseError::IncorrectFormat({ let e: Box<dyn std::error::Error + Send + Sync + 'static> = e.into(); e }))?;
                    Ok((res, ))
                 })
                }
            }
            _ => quote! { compile_error!("Expected exactly 1 argument") },
        },
        ParserType::Split { separator } => parser_with_separator(
            &separator.clone().unwrap_or_else(|| " ".to_owned()),
            types,
            count_args,
        ),
        ParserType::Custom(s) => {
            let ident = syn::Ident::new(&s, Span::call_site());
            quote! { #ident }
        }
    };
    quote! {
        let arguments = #function_to_parse(args)?;
    }
}

fn parser_with_separator<'a>(
    separator: &str,
    types: impl Iterator<Item = &'a Type>,
    count_args: usize,
) -> quote::__private::TokenStream {
    let inner = quote! { let mut splited = s.split(#separator); };
    let i = 0..count_args;
    let inner2 = quote! {
        #(#types::from_str(splited.next().ok_or(ParseError::TooFewArguments {
            expected: #count_args,
            found: #i,
            message: format!("Expected but not found arg number {}", #i + 1),
        })?).map_err(|e|ParseError::IncorrectFormat({ let e: Box<dyn std::error::Error + Send + Sync + 'static> = e.into(); e }))?,)*
    };
    let res = quote! {
        (|s: String| {
            #inner
            let res = (#inner2);
            match splited.next() {
                Some(d) => Err(ParseError::TooManyArguments {
                    expected: #count_args,
                    found: #count_args + 1,
                    message: format!("Excess argument: {}", d),
                }),
                None => Ok(res)
            }
        })
    };
    res
}