artis-derive 0.1.2

Artis is a SQL library
Documentation
use proc_macro2::{Literal, TokenStream, TokenTree};
use quote::quote;
use syn::{
    parse_macro_input, Attribute, DataStruct, DeriveInput, GenericArgument, PathSegment, Type,
};

fn extrat_colume(v: &PathSegment) -> String {
    if v.arguments.is_empty() {
        return v.ident.to_string();
    }
    let raw = v.ident.to_string();
    match raw.as_str() {
        "Vec" => return "Vec".into(),
        "HashMap" => return "Map".into(),
        _ => {}
    };
    if let syn::PathArguments::AngleBracketed(v) = &v.arguments {
        if let GenericArgument::Type(v) = v.args.first().unwrap() {
            return extrat_type(v);
        }
    }
    "".into()
}

fn extrat_type(t: &syn::Type) -> String {
    if let Type::Path(v) = t {
        return extrat_colume(v.path.segments.first().unwrap());
    }
    return "".into();
}

#[derive(Debug, Clone)]
struct Artis {
    pub table: String,
    pub name: String,
    pub typ: String,
    pub size: Option<TokenTree>,
    pub nonull: bool,
    pub index: bool,
    pub unique: bool,
    pub primary: bool,
    pub default: String,
    pub comment: String,
    pub increment: bool,
}

impl Default for Artis {
    fn default() -> Self {
        Self {
            table: "".into(),
            name: "".into(),
            typ: "".into(),
            size: Some(Literal::i32_unsuffixed(0).into()),
            nonull: false,
            index: false,
            unique: false,
            primary: false,
            default: "".into(),
            comment: "".into(),
            increment: false,
        }
    }
}

fn extrat_literal(v: Option<TokenTree>, trim: bool) -> String {
    if v.is_none() {
        return "".into();
    }
    if trim {
        v.unwrap().to_string().trim().trim_matches('"').to_string()
    } else {
        v.unwrap().to_string().trim().replace("\"", "'")
    }
}

impl From<TokenStream> for Artis {
    fn from(value: TokenStream) -> Self {
        let mut itr = value.into_iter();
        let mut artis = Artis::default();
        while let Some(v) = itr.next() {
            let raw = v.to_string();
            match raw.as_str() {
                "table" => {
                    itr.next();
                    artis.table = extrat_literal(itr.next(), true);
                }
                "type" => {
                    itr.next();
                    artis.typ = extrat_literal(itr.next(), true);
                }
                "size" => {
                    itr.next();
                    artis.size = itr.next(); //ktol extrat_literal(itr.next()).parse::<i32>().unwrap();
                }
                "default" => {
                    itr.next();
                    artis.default = extrat_literal(itr.next(), false);
                }
                "comment" => {
                    itr.next();
                    artis.comment = extrat_literal(itr.next(), false);
                }
                "INDEX" => {
                    artis.index = true;
                }
                "UNIQUE" => {
                    artis.unique = true;
                }
                "NOT_NULL" => {
                    artis.nonull = true;
                }
                "PRIMARY" => {
                    artis.primary = true;
                    artis.nonull = true;
                }
                "AUTO_INCREMENT" => {
                    artis.increment = true;
                }
                _ => {}
            }
        }
        artis
    }
}

fn extrat_attrs(list: Vec<Attribute>) -> Option<Artis> {
    for v in list {
        let meta = &v.meta.require_list().unwrap();
        if !meta.path.is_ident("artis") {
            continue;
        }
        return Some(meta.tokens.clone().into());
    }
    None
}

fn extend_feilds(v: &DataStruct) -> Vec<Artis> {
    let mut fields: Vec<Artis> = vec![];
    for field in &v.fields {
        let mut artis = Artis::default();
        if let Some(v) = extrat_attrs(field.attrs.clone()) {
            artis = v;
        }
        artis.name = field.ident.as_ref().unwrap().to_string();
        if artis.typ.is_empty() {
            artis.typ = format!(":{}", extrat_type(&field.ty));
        }
        fields.push(artis);
    }
    fields
}

#[proc_macro_derive(Artis, attributes(artis))]
pub fn device_artis(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input: DeriveInput = parse_macro_input!(input);
    let name = input.ident;
    let mut table = format!("{}s", name.to_string().to_lowercase());
    if let Some(v) = extrat_attrs(input.attrs) {
        if !v.table.is_empty() {
            table = v.table
        }
    }

    let mut inx_quote: Vec<TokenStream> = vec![];
    let mut com_quote: Vec<TokenStream> = vec![];
    let mut primary = String::new();
    if let syn::Data::Struct(s) = input.data {
        let fields = extend_feilds(&s);
        for field in fields {
            let name = &field.name;
            let colume = field.typ;
            let size = field.size;
            let nullable = !field.nonull;
            let default = field.default;
            let comment = field.comment;
            let increment = field.increment;
            let quote = quote! {artis::migrator::ColumeMeta {
                name:#name.into(),
                colume: #colume.into(),
                size: #size,
                nullable: #nullable,
                default: #default.into(),
                comment: #comment.into(),
                increment:#increment
            }};
            com_quote.push(quote.into());

            if field.primary {
                primary = field.name;
                continue;
            }
            if field.unique {
                inx_quote.push(quote! {
                    artis::migrator::IndexMeta::Unique(#name.into())
                });
                continue;
            }
            if field.index {
                inx_quote.push(quote! {
                    artis::migrator::IndexMeta::Index(#name.into())
                });
            }
        }
    }
    quote! {
        impl artis::migrator::ArtisMigrator for #name {
            fn migrator() -> artis::migrator::TableMeta {
                artis::migrator::TableMeta {
                    name: #table.into(),
                    primary: #primary.into(),
                    columes: vec![#(#com_quote,)*],
                    indexs: vec![#(#inx_quote,)*]
                }
            }
        }
    }
    .into()
}