oswilno/crates/oswilno-actix-admin/src/lib.rs

152 lines
4.3 KiB
Rust
Raw Normal View History

2023-07-27 17:36:30 +02:00
extern crate proc_macro;
use std::iter::Peekable;
use proc_macro::token_stream::IntoIter;
use proc_macro::{TokenStream, TokenTree};
fn expect_iden<F: FnMut(String) -> bool>(s: &mut Peekable<IntoIter>, mut name: F) {
let token = s.next().unwrap();
match token {
TokenTree::Ident(iden) => {
if !name(iden.to_string()) {
panic!("expect identifier");
}
}
_ => panic!("expect identifier got {token:?}"),
};
}
fn skip_proc_macro(s: &mut Peekable<IntoIter>) {
while let Some(token) = s.peek() {
match token {
TokenTree::Punct(p) if p.to_string().as_str() == "#" => {
s.next();
s.next();
}
_ => return,
}
}
}
#[derive(Default, Debug)]
struct VariantInfo {
rs_name: String,
db_name: String,
}
#[derive(Default, Debug)]
struct EnumDef {
name: String,
variants: Vec<VariantInfo>,
}
#[proc_macro_derive(ActixAdminEnum)]
pub fn derive_actix_admin_enum(item: TokenStream) -> TokenStream {
let clone = item.clone();
let mut it = clone.into_iter().peekable();
skip_proc_macro(&mut it);
expect_iden(&mut it, |iden| &iden == "pub");
expect_iden(&mut it, |iden| &iden == "enum");
let mut def = EnumDef::default();
expect_iden(&mut it, |iden| {
def.name = iden;
true
});
let mut it = if let TokenTree::Group(g) = it.next().unwrap() {
g.stream().into_iter().peekable()
} else {
panic!("expect enum body");
};
// Parse macros
parse_enum_variants(&mut it, &mut def);
let mut buffer = String::new();
buffer.push_str(&format!("impl std::str::FromStr for {} {{\n type Err = ();\n fn from_str(s: &str) -> Result<Self, ()> {{\n match s {{\n", def.name));
for v in def.variants {
buffer.push_str(&format!(
" {:?} => Ok({}::{}),\n",
v.db_name, def.name, v.rs_name
));
}
buffer.push_str(" _ => Err(()),\n");
buffer.push_str(" }\n }\n}");
// eprintln!("{buffer}");
buffer.as_str().parse().unwrap()
}
fn parse_enum_variants(body_it: &mut Peekable<IntoIter>, def: &mut EnumDef) {
while body_it.peek().is_some() {
if let Some(variant) = parse_enum_variant(body_it) {
def.variants.push(variant);
}
if let Some(TokenTree::Punct(p)) = body_it.peek() {
if p.to_string().as_str() == "," {
body_it.next();
}
}
}
2023-07-27 08:59:51 +02:00
}
2023-07-27 17:36:30 +02:00
fn parse_enum_variant(body_it: &mut Peekable<IntoIter>) -> Option<VariantInfo> {
let mut variant_info = VariantInfo::default();
while body_it.peek().is_some() {
if let Some(db_name) = parse_enum_variant_macro(body_it) {
variant_info.db_name = db_name;
break;
}
if let Some(TokenTree::Punct(p)) = body_it.peek() {
if p.to_string().as_str() == "," {
body_it.next();
}
}
}
let iden = body_it.next().expect("No variant name");
variant_info.rs_name = iden.to_string();
Some(variant_info)
}
fn parse_enum_variant_macro(body_it: &mut Peekable<IntoIter>) -> Option<String> {
let (_punc, group) = (
body_it.next().expect("No # for macro"),
body_it.next().expect("No macro body"),
);
// eprintln!("{punc:#?} {group:#?}");
let mut it = if let TokenTree::Group(g) = group {
// parse #[sea_orm]
g.stream().into_iter().peekable()
} else {
panic!("Unexpected token after #: {group:?}");
};
let token = it.next();
if let Some(TokenTree::Ident(iden)) = token {
if iden.to_string().as_str() != "sea_orm" {
eprintln!("iden is not sea_orm");
return None;
}
} else {
eprintln!("token should be iden but is {token:#?}");
return None;
}
let it = if let Some(TokenTree::Group(g)) = it.next() {
g.stream().into_iter().peekable()
} else {
return None;
};
let mut it = it.skip_while(|t| match t {
TokenTree::Ident(id) => id.to_string().as_str() != "string_value",
_ => true,
});
let (_id, _punct, literal) = (it.next(), it.next(), it.next());
2023-07-27 08:59:51 +02:00
2023-07-27 17:36:30 +02:00
match literal {
Some(TokenTree::Literal(l)) => Some(l.to_string().replace('\"', "")),
_ => None,
2023-07-27 08:59:51 +02:00
}
}