use anyhow::Result;
use askama::Template;
use convert_case::{Case, Casing};
use proc_macro::{Ident, TokenStream, TokenTree};
use std::collections::VecDeque;
#[derive(Template)]
#[template(path = "builder.j2", escape = "none")]
pub struct BuilderContext {
name: String,
fields: Vec<Fd>,
contains: fn(haystack: &[&str], needle: &str) -> bool,
uppersnake: fn(s: &str) -> String,
parse_bool: fn(s: &str) -> bool,
}
#[derive(Debug, Default)]
struct Fd {
name: String,
typ: String,
optional: bool,
attr_name: String,
attr_default: String,
attr_ext: String,
attr_ext_post_with: String,
}
impl Fd {
pub fn new(name: &[TokenTree], typ: &[TokenTree]) -> Self {
let mut attr_name: String = String::from("");
let mut attr_default: String = String::from("");
let mut attr_ext: String = String::from("");
let mut attr_ext_post_with: String = String::from("");
for item in name {
if let TokenTree::Group(g) = item {
let mut g = g.stream().into_iter();
let ident = g.next().unwrap();
if ident.to_string() == "env_config" {
let ident = g.next().unwrap();
match ident {
TokenTree::Group(g) => {
let attrs = get_struct_attribute(g.stream());
for item in attrs {
match item.0.as_str() {
"name" => {
attr_name = item.1;
}
"default" => {
attr_default = item.1;
}
"ext" => {
attr_ext = item.1;
}
"ext_post_with" => {
attr_ext_post_with = item.1;
}
_ => {}
}
}
}
_ => {}
}
break;
}
}
}
let typ = typ
.iter()
.map(|v| match v {
TokenTree::Ident(n) => n.to_string(),
TokenTree::Punct(p) => p.as_char().to_string(),
e => panic!("Expect ident, but got {:?}", e),
})
.collect::<Vec<_>>();
match name.last() {
Some(TokenTree::Ident(name)) => {
let (typ, optional) = if typ[0].as_str() == "Option" {
(&typ[2..typ.len() - 1], true)
} else {
(&typ[..], false)
};
Self {
name: name.to_string(),
typ: typ.join(""),
optional,
attr_name,
attr_default,
attr_ext,
attr_ext_post_with
}
}
e => panic!("Expect ident, but got {:?}", e),
}
}
}
impl BuilderContext {
fn new(input: TokenStream) -> Self {
let (name, input) = split(input);
let fields = get_struct_fields(input);
Self {
name: name.to_string(),
fields,
contains: |haystack, needle| haystack.contains(&needle),
uppersnake: |s| s.to_case(Case::UpperSnake),
parse_bool: |s| to_bool(s),
}
}
pub fn render(input: TokenStream) -> Result<String> {
let template = Self::new(input);
Ok(template.render()?)
}
}
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;
}
}
}
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 get_struct_fields(input: TokenStream) -> Vec<Fd> {
let input = input.into_iter().collect::<Vec<_>>();
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<_>>()
})
.filter(|tokens| tokens.len() == 2)
.map(|tokens| Fd::new(tokens[0], tokens[1]))
.collect()
}
fn get_struct_attribute(input: TokenStream) -> Vec<(String, String)> {
let input = input.into_iter().collect::<Vec<_>>();
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<_>>()
})
.filter(|tokens| tokens.len() == 2)
.map(|tokens| {
(
tokens[0]
.last()
.unwrap()
.to_string()
.trim_matches(|c: char| c == '"' || c == '\'')
.to_string(),
tokens[1]
.last()
.unwrap()
.to_string()
.trim_matches(|c: char| c == '"' || c == '\'')
.to_string(),
)
})
.collect()
}
fn to_bool(s: impl Into<String>) -> bool {
match s.into().parse::<bool>() {
Ok(b) => b,
_ => false,
}
}