use super::ok_or_compile_err;
use quote::{quote, ToTokens,};
use syn::{
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
parse::Parser,
Error as ParseError,
Expr, Generics,
Ident, Lit, Meta,
Result as ParseResult,
Token
};
use proc_macro2::TokenStream as TokenStream2;
use proc_macro::TokenStream;
use if_chain::if_chain;
#[derive(Copy, Clone, Default)]
enum ConfigType {
#[default]
Toml,
Ron
}
#[derive(Clone, Default)]
struct ConfigData {
file_name: String,
save_dir: String,
config_type: Option<ConfigType>
}
impl ConfigData {
fn parse(input: TokenStream2) -> ParseResult<Self> {
if input.is_empty() {
Err(ParseError::new(input.span(), "Options \"file_name\" and \"save_dir\" are missing!"))
}
else {
let mut data = Self::default();
let options = <Punctuated<Meta, Token![,]>>::parse_terminated.parse2(input.clone())?;
for option in options {
match option {
Meta::List(meta_list) => {
let name = meta_list.path.require_ident()?;
let type_id = syn::parse2::<Ident>(meta_list.tokens.clone())?;
if name == "cfg_type"{
if type_id == "toml" {
data.config_type = Some(ConfigType::Toml);
}
else if type_id == "ron" {
data.config_type = Some(ConfigType::Ron);
}
else {
let msg = format!("Unknown config type {type_id}! Valid config type are \"toml\" and \"ron\"");
return Err(ParseError::new(input.span(), msg));
}
}
else {
let msg = format!("Unknown option {name}! Valid options are \"cfg_type\", \"file_name\", and \"save_dir\"");
return Err(ParseError::new(input.span(), msg));
}
}
Meta::NameValue(meta_val) => {
let name = meta_val.path.require_ident()?;
if_chain! {
if let Expr::Lit(literal) = &meta_val.value;
if let Lit::Str(str_val) = &literal.lit;
then {
if name == "file_name" {
data.file_name = str_val.value();
}
else if name == "save_dir" {
data.save_dir = str_val.value();
}
}
else {
let msg = format!("Unknown option {name}! Valid options are \"cfg_type\", \"file_name\", and \"save_dir\"");
return Err(ParseError::new(input.span(), msg));
}
}
}
_ => return Err(ParseError::new(input.span(), "Invalid Option!"))
}
}
if data.file_name.is_empty() {
Err(ParseError::new(input.span(), "Option \"file_name\" is missing!"))
}
else if data.save_dir.is_empty() {
Err(ParseError::new(input.span(), "Option \"save_dir\" is missing!"))
}
else {
Ok(data)
}
}
}
}
pub fn gen_config_impl(type_def: &impl ToTokens, type_name: &Ident, generics: &Generics, attr_args: TokenStream) -> TokenStream2 {
let cfg_data = ok_or_compile_err!(ConfigData::parse(attr_args.into()));
let file_name = &cfg_data.file_name;
let save_dir = &cfg_data.save_dir;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let cfg_type_def: Option<TokenStream2> = if let Some(cfg_type) = cfg_data.config_type {
let type_name: Ident = match cfg_type {
ConfigType::Ron => parse_quote!(Ron),
ConfigType::Toml => parse_quote!(Toml)
};
Some(quote!(const TYPE: rust_utils::config::ConfigType = rust_utils::config::ConfigType::#type_name;))
}
else { None };
quote! {
#[derive(serde::Deserialize, serde::Serialize)]
#type_def
impl #impl_generics rust_utils::config::Config for #type_name #ty_generics #where_clause {
const FILE_NAME: &'static str = #file_name;
#cfg_type_def
fn get_save_dir() -> std::path::PathBuf {
Self::get_config_root()
.join(#save_dir)
}
}
}
}