db_meta_derive 0.1.0

db-meta-derive is a wrapper around PostgresSOL using tokio-postgres
Documentation
use anyhow::Result;
use askama::Template;
use proc_macro::{Ident, TokenStream, TokenTree};
use std::collections::VecDeque;

#[derive(Template, Debug)]
#[template(path = "builder.j2", escape = "none")]
pub struct BuilderContext {
    name: String,         // 结构体名称
    meta_name: String,  // 元信息结构体名称
    fields: Vec<Fd>,      // 结构体字段集合
    metas: Vec<Meta>,     // 结构体属性集合
}

/// 描述 struct 的每个 field
#[derive(Debug, Default)]
struct Fd {
    name: String, // 字段名称
    ty: String,   // 类型
    attrs: Vec<FdAttr>,
    #[allow(dead_code)]
    optional: bool, // 是否是Option
}

#[derive(Debug)]
struct FdAttr {
    name: String,
    items: Vec<Item>,
}

#[derive(Debug)]
struct Meta {
    name: String,
    items: Vec<Item>,
}

#[derive(Debug)]
struct Item {
    key: String,
    val: String,
}
impl Item {
    pub fn new(tokens: Vec<&[TokenTree]>) -> Self {
        if tokens.len() == 1 {
            match tokens[0].last() {
                Some(TokenTree::Ident(name)) => Self {
                    key: name.to_string(),
                    val: "".to_string(),
                },
                e => panic!("Expect ident, got {:?}", e),
            }
        } else {
            let ident = match tokens[0].first() {
                Some(TokenTree::Ident(name)) => Some(name),
                e => panic!("Expect ident, got {:?}", e),
            };
            let lit = match tokens[1].last() {
                Some(TokenTree::Literal(lit)) => Some(lit.to_string().replace("\"", "")),
                e => panic!("Expect ident, got {:?}", e),
            };
            Self {
                key: ident.unwrap().to_string(),
                val: lit.unwrap(),
            }
        }
    }
}

impl Fd {
    pub fn new(name: &[TokenTree], ty: &[TokenTree]) -> Self {
        // 把类似 Ident("Option"), Punct('<'), Ident("String"), Punct('>) 的 ty
        // 收集成一个 String 列表,如 vec!["Option", "<", "String", ">"]
        let ty = ty
            .iter()
            .map(|v| match v {
                TokenTree::Ident(n) => n.to_string(),
                TokenTree::Punct(p) => p.as_char().to_string(),
                e => panic!("Expect ident, got {:?}", e),
            })
            .collect::<Vec<_>>();

        // 取出字段属性,例如:#[id(type="String", length=128)]
        let mut group_arr = vec![];
        for v in name.into_iter() {
            if let TokenTree::Group(g) = v.clone() {
                group_arr.push(g.stream());
            }
        }
        let attrs = get_field_attr(group_arr);

        let fd = match name.last() {
            Some(TokenTree::Ident(name)) => {
                // 如果 ty 第 0 项是 Option,那么从第二项取到倒数第一项
                // 取完后上面的例子中的 ty 会变成 ["String"],optional = true
                let (ty, optional) = if ty[0].as_str() == "Option" {
                    (&ty[2..ty.len() - 1], true)
                } else {
                    (&ty[..], false)
                };
                Self {
                    name: name.to_string(),
                    ty: ty.join(""), // 把 ty join 成字符串
                    attrs,
                    optional,
                }
            }
            e => panic!("Expect ident, got {:?}", e),
        };
        fd
    }
}

impl BuilderContext {
    /// 从 TokenStream 中提取信息,构建 BuilderContext
    fn new(input: TokenStream) -> Self {
        let metas = get_attr_meta(split_metas(input.clone()));
        let (name, input) = split(input);
        let fields = get_struct_fields(input);
        Self {
            meta_name: format!("{}Sql", name),
            name: name.to_string(),
            fields,
            metas,
        }
    }

    /// 把模板渲染成字符串代码
    pub fn render(input: TokenStream) -> Result<String> {
        let template = Self::new(input);
        Ok(template.render()?)
    }
}

/// 把 TokenStream 分出 struct 的名字,和包含 fields 的 TokenStream
fn split(input: TokenStream) -> (Ident, TokenStream) {
    let mut input = input.into_iter().collect::<VecDeque<_>>();
    while let Some(item) = input.pop_front() {
        if let TokenTree::Ident(v) = item {
            if v.to_string() == "struct" {
                break;
            }
        }
    }

    // struct 后面,应该是 struct name
    let ident;
    if let Some(TokenTree::Ident(v)) = input.pop_front() {
        ident = v;
    } else {
        panic!("Didn't find struct name");
    }

    let mut group = None;
    for item in input {
        if let TokenTree::Group(g) = item {
            group = Some(g);
            break;
        }
    }

    (ident, group.expect("Didn't find field group").stream())
}

fn split_metas(input: TokenStream) -> Vec<TokenStream> {
    let mut input = input.into_iter().collect::<VecDeque<_>>();
    while let Some(item) = input.pop_back() {
        if let TokenTree::Ident(v) = item {
            if v.to_string() == "struct" {
                break;
            }
        }
    }
    let mut v = vec![];
    for item in input {
        if let TokenTree::Group(g) = item {
            v.push(g.stream());
        }
    }
    v
}

fn get_attr_meta(inputs: Vec<TokenStream>) -> Vec<Meta> {
    let mut metas = vec![];
    for input in inputs {
        let mut input = input.into_iter().collect::<VecDeque<_>>();
        let mut ident = None;
        let mut items = vec![];
        while let Some(item) = input.pop_front() {
            if let TokenTree::Ident(v) = item.clone() {
                ident = Some(v);
            }
            if let TokenTree::Group(g) = item {
                let input = g.stream().into_iter().collect::<Vec<_>>();

                items = input
                    .split(|v| match v {
                        TokenTree::Punct(p) => p.as_char() == ',',
                        _ => false,
                    })
                    .map(|tokens| {
                        tokens
                            .split(|v| match v {
                                TokenTree::Punct(p) => p.as_char() == '=',
                                _ => false,
                            })
                            .collect::<Vec<_>>()
                    })
                    .map(|tokens| Item::new(tokens))
                    .collect();
            }
        }
        metas.push(Meta {
            name: ident.expect("meta didn't have name").to_string(),
            items,
        });
    }
    metas
}

fn get_struct_fields(input: TokenStream) -> Vec<Fd> {
    let input = input.into_iter().collect::<VecDeque<_>>();

    let input = input.into_iter().collect::<Vec<_>>();
    input
        .split(|v| {
            match v {
                // 先用 ',' 切出来一个个包含 field 所有信息的 &[TokenTree]
                TokenTree::Punct(p) => p.as_char() == ',',
                _ => false,
            }
        })
        .map(|tokens| {
            let re = tokens
                .split(|v| match v {
                    // 再用 ':' 把 &[TokenTree] 切成 [&[TokenTree], &[TokenTree]]
                    // 它们分别对应名字和类型
                    TokenTree::Punct(p) => p.as_char() == ':',
                    _ => false,
                })
                .collect::<Vec<_>>();
            re
        })
        // 正常情况下,应该得到 [&[TokenTree], &[TokenTree]],对于切出来长度不为 2 的统统过滤掉
        .filter(|tokens| tokens.len() == 2)
        // 使用 Fd::new 创建出每个 Fd
        .map(|tokens| Fd::new(tokens[0], &tokens[1]))
        .collect()
}

fn get_field_attr(inputs: Vec<TokenStream>) -> Vec<FdAttr> {
    let mut metas = vec![];
    for input in inputs {
        let mut input = input.into_iter().collect::<VecDeque<_>>();
        let mut ident = None;
        let mut items = vec![];
        while let Some(item) = input.pop_front() {
            if let TokenTree::Ident(v) = item.clone() {
                ident = Some(v);
            }
            if let TokenTree::Group(g) = item {
                let input = g.stream().into_iter().collect::<Vec<_>>();

                items = input
                    .split(|v| match v {
                        TokenTree::Punct(p) => p.as_char() == ',',
                        _ => false,
                    })
                    .map(|tokens| {
                        tokens
                            .split(|v| match v {
                                TokenTree::Punct(p) => p.as_char() == '=',
                                _ => false,
                            })
                            .collect::<Vec<_>>()
                    })
                    .map(|tokens| Item::new(tokens))
                    .collect();
            }
        }
        metas.push(FdAttr {
            name: ident.expect("meta didn't have name").to_string(),
            items,
        });
    }
    metas
}