This repository has been archived on 2024-07-25. You can view files and clone it, but cannot push or open issues or pull requests.
enve/itconfig-macro/src/parse.rs

263 lines
8.3 KiB
Rust
Raw Normal View History

2020-03-12 23:20:34 +03:00
use crate::ast::*;
use syn::parse::{Parse, ParseStream, Result, ParseBuffer};
use syn::token::{FatArrow, Comma, Colon, Brace, Lt};
use syn::{braced, parenthesized, Type, Expr, Token, Lit, Attribute, Meta, MetaNameValue, Error, parse_str, MetaList, NestedMeta};
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use syn::ext::IdentExt;
use quote::quote;
fn fill_env_prefix(
prefix: String
) -> Box<dyn Fn(Namespace) -> 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<Variable>,
namespaces: &mut Vec<Namespace>,
) -> Result<()> {
let attributes: Vec<Attribute> = 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") {
namespace.env_prefix = parse_attribute(attr, "env_prefix", &namespace.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") {
variable.env_name = parse_attribute(attr, "env_name", &variable.env_name)?;
} else {
variable.meta.push(attr);
}
}
variables.push(variable);
}
Ok(())
}
fn parse_attribute(attr: Attribute, name: &'static str, var: &Option<String>) -> Result<Option<String>> {
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(Some(lit_str.value()))
}
_ => {
let message = format!("expected #[{} = \"...\"]", &name);
Err(Error::new_spanned(attr, message))
}
}
}
impl Parse for RootNamespace {
fn parse(input: ParseStream) -> Result<Self> {
let mut name: Option<Ident> = None;
let mut with_module = true;
let mut meta: Vec<Attribute> = vec![];
let attributes: Vec<Attribute> = 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 = format!("expected #[config(name = \"...\")] or #[config(unwrap)]");
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 {
Err(Error::new_spanned(attr, message))?;
}
},
NestedMeta::Meta(Meta::Path(path)) => {
if path.is_ident("unwrap") {
name = None;
with_module = false;
} else {
Err(Error::new_spanned(attr, message))?;
}
},
_ => {
Err(Error::new_spanned(attr, message))?;
}
}
}
_ => {
let message = format!("expected #[config(...)]");
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<Variable> = vec![];
let mut namespaces: Vec<Namespace> = 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.clone()))
.collect();
Ok(RootNamespace {
name,
variables,
namespaces,
meta,
})
}
}
impl Parse for Namespace {
fn parse(input: ParseStream) -> Result<Self> {
let name: Ident = input.parse()?;
let mut variables: Vec<Variable> = vec![];
let mut namespaces: Vec<Namespace> = vec![];
let content;
braced!(content in input);
while !content.is_empty() {
parse_namespace_content(&content, &mut variables, &mut namespaces)?;
}
input.parse::<Comma>().ok();
Ok(Namespace {
name,
variables,
namespaces,
env_prefix: None,
meta: vec![],
})
}
}
impl Parse for Variable {
fn parse(input: ParseStream) -> Result<Self> {
let is_static = input.parse::<Token![static]>().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::<Colon>()?;
input.parse()?
} else {
parse_str("&'static str")?
};
if is_concat {
input.parse::<Lt>()?;
let content;
parenthesized!(content in input);
let mut tmp_vec: Vec<TokenStream2> = 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(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::<Comma>().ok();
}
concat_parts = Some(tmp_vec);
} else {
initial = input.parse::<FatArrow>().ok()
.and_then(|_| input.parse::<Expr>().ok());
};
input.parse::<Comma>().ok();
Ok(Variable {
is_static,
name,
ty,
initial,
concat_parts,
env_name: None,
meta: vec![],
})
}
}