miko-macros 0.1.7

Macros for miko
Documentation
use quote::ToTokens;
use std::collections::HashMap;
use std::fmt;
use std::fmt::{Debug, Formatter};
use syn::{Error, FnArg, LitStr, Meta, Type, TypePath};

#[derive(Clone)]
pub struct RouteFnArg {
    pub ident: syn::Ident,
    pub ty: Type,
    pub attrs: Vec<syn::Attribute>,
    pub is_option: bool,
    pub mark: HashMap<String, ArgAttr>,
    pub origin: FnArg,
}
impl Debug for RouteFnArg {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_struct("RouteFnArg")
            .field("ident", &self.ident)
            .field("ty", &self.ty.to_token_stream().to_string())
            .field("mark", &self.mark)
            .finish()
    }
}
#[derive(Debug, Clone)]
pub struct ArgAttr {
    pub map: HashMap<String, String>,
}

impl RouteFnArg {
    pub fn from_punctuated(
        inputs: &mut syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
    ) -> Vec<RouteFnArg> {
        let mut out = Vec::new();
        for input in inputs {
            let input_clone = input.clone();
            match input {
                FnArg::Typed(pat) => {
                    let mut mark = HashMap::new();
                    let ident = match &*pat.pat {
                        syn::Pat::Ident(pat_ident) => Some(pat_ident.ident.clone()),
                        syn::Pat::TupleStruct(pat_ts) => {
                            let mut pat_ts = pat_ts.clone();
                            while let Some(syn::Pat::TupleStruct(pat_tsn)) = pat_ts.elems.first() {
                                pat_ts = pat_tsn.clone();
                            }
                            if let syn::Pat::Ident(pat_ident) = pat_ts.elems.first().unwrap() {
                                Some(pat_ident.ident.clone())
                            } else {
                                None
                            }
                        }
                        _ => None,
                    };
                    let (is_option, _option_ty) = is_option(&pat.ty);
                    if ident.is_none() {
                        panic!("{:?}", pat.pat.to_token_stream().to_string());
                        panic!("RouteFnArg must have an ident");
                    }
                    for attr in &pat.attrs {
                        let ident_str = attr.path().get_ident().unwrap().to_string();
                        mark.insert(ident_str, parse_attr(attr));
                    }
                    let rfa = RouteFnArg {
                        ident: ident.unwrap(),
                        ty: *pat.ty.clone(),
                        is_option,
                        attrs: vec![],
                        mark,
                        origin: input_clone,
                    };
                    out.push(rfa);
                }
                _ => {}
            }
        }
        out
    }
}

pub trait IntoFnArgs {
    fn gen_fn_args(&self, callback: impl Fn(&RouteFnArg) -> FnArgResult) -> Vec<FnArg>;
}

impl IntoFnArgs for Vec<RouteFnArg> {
    fn gen_fn_args(&self, callback: impl Fn(&RouteFnArg) -> FnArgResult) -> Vec<FnArg> {
        let mut out = Vec::new();
        for rfa in self {
            let mut clone = rfa.origin.clone();
            match callback(rfa) {
                FnArgResult::Remove => {}
                FnArgResult::Keep => out.push(rfa.origin.clone()),
                FnArgResult::Replace(new) => out.push(new),
                FnArgResult::RemoveAttr => {
                    if let FnArg::Typed(ref mut pat) = clone {
                        pat.attrs.clear();
                    }
                    out.push(clone);
                }
            }
        }
        out
    }
}

pub fn is_option(ty: &Type) -> (bool, Option<Type>) {
    let Type::Path(TypePath { path, .. }) = ty else {
        return (false, None);
    };
    let last = path.segments.last().unwrap();
    if last.ident == "Option" {
        match &last.arguments {
            syn::PathArguments::AngleBracketed(args) => {
                let ty = args.args.first().unwrap();
                let ty = match ty {
                    syn::GenericArgument::Type(ty) => ty,
                    _ => panic!("Option must have a type"),
                };
                (true, Some(ty.clone()))
            }
            _ => (false, None),
        }
    } else {
        (false, None)
    }
}

pub fn parse_attr(attr: &syn::Attribute) -> ArgAttr {
    let mut map = HashMap::new();
    let meta = &attr.meta;
    if let Meta::List(list) = meta {
        list.parse_nested_meta(|nmeta| {
            let key = nmeta.path.get_ident().unwrap().to_string();
            let val: Result<LitStr, Error> = nmeta.value().and_then(|v| v.parse());
            match val {
                Ok(val) => {
                    map.insert(key, val.value());
                }
                Err(_) => {
                    map.insert(key.clone(), key);
                }
            }
            Ok(())
        })
    } else if let Meta::Path(_path) = meta {
        Ok(())
    } else {
        panic!("not a list attr")
    }
    .expect("P IE");
    ArgAttr { map }
}

pub enum FnArgResult {
    Remove,
    Keep,
    Replace(FnArg),
    RemoveAttr,
}