use crate::ast::*; use crate::utils::{maybe_supported_box, SupportedBox, VecBoxParams}; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::quote; use syn::ext::IdentExt; use syn::parse::{Parse, ParseBuffer, ParseStream, Result}; use syn::token::{Brace, Colon, Comma, FatArrow, Lt}; use syn::{ braced, parenthesized, parse_str, Attribute, Error, Expr, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Token, Type, }; fn fill_env_prefix(prefix: String) -> Box Namespace> { Box::new(move |mut ns| { let env_prefix = match &ns.env_prefix { None => { let env_prefix = format!("{}{}_", prefix, ns.name.clone().to_string()); ns.env_prefix = Some(env_prefix.clone()); env_prefix } Some(env_prefix) => env_prefix.clone(), }; if !ns.namespaces.is_empty() { ns.namespaces = ns .namespaces .into_iter() .map(fill_env_prefix(ns.env_prefix.clone().unwrap())) .collect() } if !ns.variables.is_empty() { ns.variables = ns .variables .into_iter() .map(|mut var| { if var.env_name.is_none() { var.env_name = Some( format!("{}{}", env_prefix.clone(), &var.name.to_string()) .to_uppercase(), ); } var }) .collect() } ns }) } fn parse_namespace_content( input: &ParseBuffer, variables: &mut Vec, namespaces: &mut Vec, ) -> Result<()> { let attributes: Vec = input.call(Attribute::parse_outer)?; if input.peek2(Brace) { let mut namespace: Namespace = input.parse()?; for attr in attributes { if attr.path.is_ident("env_prefix") { let env_prefix = parse_attribute(attr, "env_prefix", &namespace.env_prefix)?; namespace.env_prefix = Some(env_prefix); } else { namespace.meta.push(attr); } } namespaces.push(namespace); } else { let mut variable: Variable = input.parse()?; for attr in attributes { if attr.path.is_ident("env_name") { let env_name = parse_attribute(attr, "env_name", &variable.env_name)?; variable.env_name = Some(env_name); } else { match variable.supported_box { Some(SupportedBox::Vec(params)) if attr.path.is_ident("sep") => { let sep = parse_attribute(attr, "sep", ¶ms.sep_opt())?; variable.supported_box = Some(SupportedBox::Vec(VecBoxParams::new(Some(sep)))); } _ => variable.meta.push(attr), } } } variables.push(variable); } Ok(()) } fn parse_attribute(attr: Attribute, name: &'static str, var: &Option) -> Result { if var.is_some() { let message = format!("You cannot use {} meta twice", &name); return Err(Error::new_spanned(attr, message)); } match attr.parse_meta()? { Meta::NameValue(MetaNameValue { lit: Lit::Str(lit_str), .. }) => Ok(lit_str.value()), _ => { let message = format!("expected #[{} = \"...\"]", &name); Err(Error::new_spanned(attr, message)) } } } impl Parse for RootNamespace { fn parse(input: ParseStream) -> Result { let mut name: Option = None; let mut with_module = true; let mut meta: Vec = vec![]; let attributes: Vec = input.call(Attribute::parse_inner)?; for attr in attributes { if attr.path.is_ident("config") { match attr.parse_meta()? { Meta::List(MetaList { nested, .. }) => { let message = "expected #[config(name = \"...\")] or #[config(unwrap)]".to_string(); match nested.first().unwrap() { NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit: Lit::Str(lit_str), .. })) => { if path.is_ident("name") { name = Some(Ident::new(&lit_str.value(), Span::call_site())); } else { return Err(Error::new_spanned(attr, message)); } } NestedMeta::Meta(Meta::Path(path)) => { if path.is_ident("unwrap") { name = None; with_module = false; } else { return Err(Error::new_spanned(attr, message)); } } _ => { return Err(Error::new_spanned(attr, message)); } } } _ => { let message = "expected #[config(...)]".to_string(); return Err(Error::new_spanned(attr, message)); } } } else { meta.push(attr); } } if with_module && name.is_none() { name = Some(Ident::new("config", Span::call_site())); } let mut variables: Vec = vec![]; let mut namespaces: Vec = vec![]; while !input.is_empty() { parse_namespace_content(&input, &mut variables, &mut namespaces)?; } let prefix = String::new(); let namespaces = namespaces .into_iter() .map(fill_env_prefix(prefix)) .collect(); Ok(RootNamespace { name, variables, namespaces, meta, }) } } impl Parse for Namespace { fn parse(input: ParseStream) -> Result { let name: Ident = input.parse()?; let mut variables: Vec = vec![]; let mut namespaces: Vec = vec![]; let content; braced!(content in input); while !content.is_empty() { parse_namespace_content(&content, &mut variables, &mut namespaces)?; } input.parse::().ok(); Ok(Namespace { name, variables, namespaces, env_prefix: None, meta: vec![], }) } } impl Parse for Variable { fn parse(input: ParseStream) -> Result { let is_static = input.parse::().ok().is_some(); let name: Ident = input.parse()?; let is_concat = input.peek(Lt); let mut concat_parts = None; let mut initial = None; let ty: Type = if is_concat { parse_str("String")? } else if input.peek(Colon) { input.parse::()?; input.parse()? } else { parse_str("&'static str")? }; let supported_box = maybe_supported_box(&ty); if is_concat { input.parse::()?; let content; parenthesized!(content in input); let mut tmp_vec: Vec = vec![]; while !content.is_empty() { if content.peek(Ident::peek_any) { let concat_var: Variable = content.parse()?; let name = &concat_var.name; let env_name = &concat_var .env_name .clone() .unwrap_or_else(|| name.to_string()); let get_variable = if concat_var.initial.is_some() { let initial = concat_var.initial.as_ref().unwrap(); quote!(::itconfig::get_env_or_set_default(#env_name, #initial)) } else { quote!(::itconfig::get_env_or_panic(#env_name)) }; tmp_vec.push(get_variable); } else { let part: Lit = content.parse()?; tmp_vec.push(quote!(#part.to_string())); } content.parse::().ok(); } concat_parts = Some(tmp_vec); } else { initial = input .parse::() .ok() .and_then(|_| input.parse::().ok()); }; input.parse::().ok(); Ok(Variable { supported_box, is_static, name, ty, initial, concat_parts, env_name: None, meta: vec![], }) } }