From 3554fe95378a8d4276174c3242876808c9a74b02 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Thu, 12 Mar 2020 23:20:34 +0300 Subject: [PATCH] feat: add itconfig proc macro --- Cargo.toml | 2 + README.md | 33 +- examples/diesel/src/db.rs | 4 +- examples/diesel/src/main.rs | 5 +- examples/hyper/Cargo.toml | 1 + examples/hyper/src/main.rs | 10 +- examples/rocket/src/main.rs | 7 +- itconfig-macro/Cargo.toml | 30 + itconfig-macro/src/ast.rs | 30 + itconfig-macro/src/expand.rs | 170 +++++ itconfig-macro/src/lib.rs | 338 ++++++++++ itconfig-macro/src/parse.rs | 262 ++++++++ itconfig-tests/Cargo.toml | 2 +- itconfig-tests/benches/main_benches.rs | 8 +- itconfig-tests/tests/config_macro.rs | 495 ++++++++------ itconfig/Cargo.toml | 14 +- itconfig/README.md | 33 +- itconfig/src/cfg.rs | 33 + itconfig/src/lib.rs | 45 +- itconfig/src/macro.rs | 880 ------------------------- 20 files changed, 1258 insertions(+), 1144 deletions(-) create mode 100644 itconfig-macro/Cargo.toml create mode 100644 itconfig-macro/src/ast.rs create mode 100644 itconfig-macro/src/expand.rs create mode 100644 itconfig-macro/src/lib.rs create mode 100644 itconfig-macro/src/parse.rs create mode 100644 itconfig/src/cfg.rs delete mode 100644 itconfig/src/macro.rs diff --git a/Cargo.toml b/Cargo.toml index 5f42aad..f884d19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "itconfig", + "itconfig-macro", "itconfig-tests", "examples/diesel", "examples/rocket", # nightly @@ -9,5 +10,6 @@ members = [ default-members = [ "itconfig", + "itconfig-macro", "itconfig-tests", ] diff --git a/README.md b/README.md index 511b893..fe772c6 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,29 @@ where you need variable. It uses little bit memory, but configuration lifetime i as application lifetime. Because of it I decided to create my own library. +## Installation + +These macros require a Rust compiler version 1.31 or newer. + +Add `itconfig = { version = "1.0", features = ["macro"] }` as a dependency in `Cargo.toml`. + +`Cargo.toml` example: + +```toml +[package] +name = "my-crate" +version = "0.1.0" +authors = ["Me "] + +[dependencies] +itconfig = { version = "1.0", features = ["macro"] } +``` + + ## Example usage ```rust -#[macro_use] extern crate itconfig; +use itconfig::config; use std::env; //use dotenv::dotenv; @@ -69,11 +88,11 @@ fn main () { // dotenv().ok(); env::set_var("FEATURE_NEW_MENU", "t"); - cfg::init(); - assert_eq!(cfg::HOST(), String::from("127.0.0.1")); - assert_eq!(cfg::DATABASE_URL(), String::from("postgres://user:pass@localhost:5432/test")); - assert_eq!(cfg::APP:ARTICLE:PER_PAGE(), 15); - assert_eq!(cfg::FEATURE::NEW_MENU(), true); + config::init(); + assert_eq!(config::HOST(), String::from("127.0.0.1")); + assert_eq!(config::DATABASE_URL(), String::from("postgres://user:pass@localhost:5432/test")); + assert_eq!(config::APP:ARTICLE:PER_PAGE(), 15); + assert_eq!(config::FEATURE::NEW_MENU(), true); } ``` @@ -112,6 +131,7 @@ cargo test * [x] Add nested namespaces * [x] Support meta for namespaces * [x] Support array type +* [x] Rewrite to proc macro * [ ] Support hashmap type * [ ] Support custom env type * [ ] Common configuration for namespace variables @@ -121,7 +141,6 @@ cargo test * **default** - ["macro", "primitives", "static"] * **macro** - Activates `config!` macros for easy configure web application. -* **static** - Add `static` option to `config!` macros (uses optional `lazy_static` package). * **array** - Add EnvString impl for vector type (uses optional `serde_json` package). * **primitives** - Group for features: `numbers` and `bool`. * **numbers** - Group for features: `int`, `uint` and `float`. diff --git a/examples/diesel/src/db.rs b/examples/diesel/src/db.rs index 0797c4d..e60d957 100644 --- a/examples/diesel/src/db.rs +++ b/examples/diesel/src/db.rs @@ -1,9 +1,9 @@ -use super::cfg; +use super::config; use diesel::prelude::*; use diesel::pg::PgConnection; pub fn establish_connection() -> PgConnection { - let database_url = cfg::DATABASE_URL(); + let database_url = config::DATABASE_URL(); PgConnection::establish(database_url) .expect(&format!("Error connecting to {}", database_url)) } diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs index 1adc4e6..70952f3 100644 --- a/examples/diesel/src/main.rs +++ b/examples/diesel/src/main.rs @@ -1,12 +1,11 @@ #[macro_use] -extern crate itconfig; -#[macro_use] extern crate diesel; mod db; mod models; mod schema; +use itconfig::config; use dotenv::dotenv; use diesel::prelude::*; use crate::models::*; @@ -19,7 +18,7 @@ config! { fn main() { dotenv().ok(); - cfg::init(); + config::init(); let connection = db::establish_connection(); let posts = get_posts(&connection); diff --git a/examples/hyper/Cargo.toml b/examples/hyper/Cargo.toml index 2fcf87f..edd9f84 100644 --- a/examples/hyper/Cargo.toml +++ b/examples/hyper/Cargo.toml @@ -14,6 +14,7 @@ tokio = { version = "0.2", features = ["macros"] } bytes = "0.5" futures-util = { version = "0.3", default-features = false } pretty_env_logger = "0.3" +lazy_static = "1.4.0" [features] default = ["static"] diff --git a/examples/hyper/src/main.rs b/examples/hyper/src/main.rs index 1de774c..e30de5e 100644 --- a/examples/hyper/src/main.rs +++ b/examples/hyper/src/main.rs @@ -1,6 +1,4 @@ -#[macro_use] -extern crate itconfig; - +use itconfig::config; use bytes::buf::BufExt; use futures_util::{stream, StreamExt}; use hyper::client::HttpConnector; @@ -41,7 +39,7 @@ const POST_DATA: &'static str = r#"{"original": "data"}"#; async fn client_request_response(client: &Client) -> HyperResult> { let req = Request::builder() .method(Method::POST) - .uri(cfg::HYPER::JSON_API_URL()) + .uri(config::HYPER::JSON_API_URL()) .header(header::CONTENT_TYPE, "application/json") .body(Body::from(POST_DATA)) .unwrap(); @@ -113,10 +111,10 @@ async fn response_examples( #[tokio::main] async fn main() -> HyperResult<()> { - cfg::init(); + config::init(); pretty_env_logger::init(); - let addr = cfg::HYPER::HOST().parse().unwrap(); + let addr = config::HYPER::HOST().parse().unwrap(); // Share a `Client` with all `Service`s let client = Client::new(); diff --git a/examples/rocket/src/main.rs b/examples/rocket/src/main.rs index c1ad78a..a21d100 100644 --- a/examples/rocket/src/main.rs +++ b/examples/rocket/src/main.rs @@ -2,9 +2,8 @@ #[macro_use] extern crate rocket; -#[macro_use] -extern crate itconfig; +use itconfig::config; config! { ROCKET { @@ -21,9 +20,9 @@ fn index() -> &'static str { } fn main() { - cfg::init(); + config::init(); rocket::ignite() - .mount(cfg::ROCKET::BASE_URL(), routes![index]) + .mount(config::ROCKET::BASE_URL(), routes![index]) .launch(); } \ No newline at end of file diff --git a/itconfig-macro/Cargo.toml b/itconfig-macro/Cargo.toml new file mode 100644 index 0000000..a1b8d1b --- /dev/null +++ b/itconfig-macro/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "itconfig-macro" +version = "1.0.0" +authors = ["Dmitriy Pleshevskiy "] +description = "Easy build a configs from environment variables and use it in globally." +categories = ["config", "web-programming"] +keywords = ["config", "env", "configuration", "environment", "macro"] +edition = "2018" +license = "MIT" +repository = "https://github.com/icetemple/itconfig-rs" +homepage = "https://github.com/icetemple/itconfig-rs" +documentation = "https://docs.rs/itconfig" +readme = "../itconfig/README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +proc-macro = true + +[dependencies] +syn = "1.0.16" +quote = "1.0.3" +proc-macro2 = "1.0.9" + +[dev-dependencies] +itconfig = { path = "../itconfig" } +lazy_static = "1.4.0" + +[badges] +travis-ci = { repository = "icetemple/itconfig-rs" } +maintenance = { status = "actively-developed" } diff --git a/itconfig-macro/src/ast.rs b/itconfig-macro/src/ast.rs new file mode 100644 index 0000000..0ada792 --- /dev/null +++ b/itconfig-macro/src/ast.rs @@ -0,0 +1,30 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::{Type, Expr, Ident, Attribute}; + + +pub struct RootNamespace { + pub name: Option, + pub variables: Vec, + pub namespaces: Vec, + pub meta: Vec, +} + + +pub struct Namespace { + pub name: Ident, + pub variables: Vec, + pub namespaces: Vec, + pub env_prefix: Option, + pub meta: Vec, +} + + +pub struct Variable { + pub is_static: bool, + pub name: Ident, + pub ty: Type, + pub initial: Option, + pub concat_parts: Option>, + pub env_name: Option, + pub meta: Vec, +} diff --git a/itconfig-macro/src/expand.rs b/itconfig-macro/src/expand.rs new file mode 100644 index 0000000..a93727f --- /dev/null +++ b/itconfig-macro/src/expand.rs @@ -0,0 +1,170 @@ +use crate::ast::*; +use quote::{quote, ToTokens, TokenStreamExt}; +use proc_macro2::TokenStream as TokenStream2; + + +fn vec_to_token_stream_2(input: &Vec) -> Vec + where T: ToTokens +{ + input.iter() + .map(|ns| ns.into_token_stream()) + .collect() +} + + +impl ToTokens for RootNamespace { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let name = &self.name; + let variables = vec_to_token_stream_2(&self.variables); + let namespaces = vec_to_token_stream_2(&self.namespaces); + + let init_variables = self.variables.iter() + .map(|var| { + let name = &var.name; + let var_meta = vec_to_token_stream_2(&var.meta); + + quote!( + #(#var_meta)* + #name(); + ) + }) + .collect::>(); + let init_namespaces = self.namespaces.iter() + .map(|ns| { + let name = &ns.name; + let ns_meta = vec_to_token_stream_2(&ns.meta); + + quote!( + #(#ns_meta)* + #name::init(); + ) + }) + .collect::>(); + + let inner_meta: Vec = if name.is_none() { + vec![] + } else if self.meta.is_empty() { + vec![quote!(#![allow(non_snake_case)])] + } else { + vec_to_token_stream_2(&self.meta) + }; + + + let inner_rules = quote! { + #(#inner_meta)* + + #(#namespaces)* + + #(#variables)* + + pub fn init() { + #(#init_variables)* + #(#init_namespaces)* + } + }; + + tokens.append_all( + match self.name.as_ref() { + None => inner_rules, + Some(name) => quote! { + pub mod #name { + #inner_rules + } + } + } + ); + } +} + + +impl ToTokens for Namespace { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let name = &self.name; + let variables = vec_to_token_stream_2(&self.variables); + let namespaces = vec_to_token_stream_2(&self.namespaces); + let meta = vec_to_token_stream_2(&self.meta); + + let init_variables = self.variables.iter() + .map(|var| { + let name = &var.name; + let var_meta = vec_to_token_stream_2(&var.meta); + + quote!( + #(#var_meta)* + #name(); + ) + }) + .collect::>(); + let init_namespaces = self.namespaces.iter() + .map(|ns| { + let name = &ns.name; + let ns_meta = vec_to_token_stream_2(&ns.meta); + + quote!( + #(#ns_meta)* + #name::init(); + ) + }) + .collect::>(); + + tokens.append_all(quote!( + #(#meta)* + pub mod #name { + #(#namespaces)* + + #(#variables)* + + pub fn init() { + #(#init_variables)* + #(#init_namespaces)* + } + } + )) + } +} + + +impl ToTokens for Variable { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let ty = &self.ty; + let name = &self.name; + let env_name = &self.env_name.clone() + .unwrap_or(name.to_string().to_uppercase()); + let meta = vec_to_token_stream_2(&self.meta); + + let get_variable: TokenStream2 = if self.concat_parts.is_some() { + let concat_parts = self.concat_parts.as_ref().unwrap(); + quote! {{ + let value_parts: Vec = vec!(#(#concat_parts),*); + let value = value_parts.join(""); + ::std::env::set_var(#env_name, value.as_str()); + value + }} + } else if self.initial.is_some() { + let initial = self.initial.as_ref().unwrap(); + quote!(::itconfig::get_env_or_set_default(#env_name, #initial)) + } else { + quote!(::itconfig::get_env_or_panic(#env_name)) + }; + + if self.is_static { + tokens.append_all(quote!( + #(#meta)* + pub fn #name() -> #ty { + ::lazy_static::lazy_static! { + static ref #name: #ty = #get_variable; + } + + (*#name).clone() + } + )); + } else { + tokens.append_all(quote!( + #(#meta)* + pub fn #name() -> #ty { + #get_variable + } + )); + } + } +} \ No newline at end of file diff --git a/itconfig-macro/src/lib.rs b/itconfig-macro/src/lib.rs new file mode 100644 index 0000000..c3771e1 --- /dev/null +++ b/itconfig-macro/src/lib.rs @@ -0,0 +1,338 @@ +#![recursion_limit = "256"] + +mod ast; +mod parse; +mod expand; + +extern crate proc_macro; +extern crate proc_macro2; +use self::proc_macro::TokenStream; +use quote::ToTokens; +use syn::parse_macro_input; +use ast::RootNamespace; + + +/// ### _This API requires the following crate features to be activated: `macro`_ +/// +/// Creates new public mod with function fo get each environment variable of mapping. +/// +/// All variables are required and program will panic if some variables haven't value, but you +/// can add default value for specific variable. +/// +/// Starts with v0.6.0 if you don't have an optional variable, the variable is set automatically. +/// +/// Example usage +/// ------------- +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// config! { +/// DATABASE_URL: String, +/// } +/// +/// # fn main() { +/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db"); +/// # config::init(); +/// # } +/// ``` +/// +/// Config with default value +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// config! { +/// DATABASE_URL: String, +/// HOST: String => "127.0.0.1", +/// } +/// # fn main() { +/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db"); +/// # config::init(); +/// # } +/// ``` +/// +/// By default itconfig lib creates module with 'config' name. But you can use simple meta instruction +/// if you want to rename module. In the example below we renamed module to 'configuration' +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// config! { +/// #![config(name = "configuration")] +/// +/// DEBUG: bool, +/// } +/// +/// fn main() { +/// env::set_var("DEBUG", "t"); +/// +/// configuration::init(); +/// assert_eq!(configuration::DEBUG(), true); +/// } +/// ``` +/// +/// You also unwrap first config module +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// +/// config! { +/// #![config(unwrap)] +/// +/// DEBUG: bool, +/// } +/// +/// fn main() { +/// env::set_var("DEBUG", "t"); +/// +/// init(); +/// assert_eq!(DEBUG(), true); +/// } +/// ``` +/// +/// +/// Namespaces +/// ---------- +/// +/// You can use namespaces for env variables +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// +/// config! { +/// DEBUG: bool, +/// DATABASE { +/// USERNAME: String, +/// PASSWORD: String, +/// HOST: String, +/// } +/// } +/// fn main() { +/// env::set_var("DEBUG", "t"); +/// env::set_var("DATABASE_USERNAME", "user"); +/// env::set_var("DATABASE_PASSWORD", "pass"); +/// env::set_var("DATABASE_HOST", "localhost"); +/// +/// config::init(); +/// } +/// ``` +/// +/// Now you can use nested structure in namespaces without limits :) +/// +/// ```rust +/// # use itconfig::config; +/// config! { +/// FIRST { +/// SECOND { +/// THIRD { +/// FOO: bool => true, +/// } +/// } +/// } +/// } +/// # fn main() { config::init () } +/// ``` +/// +/// Namespaces supports custom meta +/// +/// ```rust +/// # use itconfig::config; +/// config! { +/// #[cfg(feature = "first")] +/// FIRST { +/// #[cfg(feature = "second")] +/// SECOND { +/// #[cfg(feature = "third")] +/// THIRD { +/// FOO: bool => true, +/// } +/// } +/// } +/// } +/// # fn main() { config::init () } +/// ``` +/// +/// Meta +/// ---- +/// +/// If you want to read custom env name for variable you can change it manually. +/// +/// **A variable in the nameespace will lose environment prefix** +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// +/// config! { +/// #[env_name = "MY_CUSTOM_NAME"] +/// PER_PAGE: i32, +/// +/// APP { +/// #[env_name = "MY_CUSTOM_NAME"] +/// RECIPES_PER_PAGE: i32, +/// } +/// } +/// +/// fn main() { +/// env::set_var("MY_CUSTOM_NAME", "95"); +/// +/// config::init(); +/// assert_eq!(config::PER_PAGE(), 95); +/// assert_eq!(config::APP::RECIPES_PER_PAGE(), 95); +/// } +/// ``` +/// +/// Also you can add custom meta for each variable. For example feature configurations. +/// +/// ```rust +/// # use itconfig::config; +/// config! { +/// #[cfg(feature = "postgres")] +/// DATABASE_URL: String, +/// +/// #[cfg(not(feature = "postgres"))] +/// DATABASE_URL: String, +/// } +/// # fn main() { } +/// ``` +/// +/// Concatenate +/// ----------- +/// +/// Try to concatenate env variable or strings or both to you env variable. It's easy! +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// config! { +/// DATABASE_URL < ( +/// "postgres://", +/// POSTGRES_USERNAME, +/// ":", +/// POSTGRES_PASSWORD, +/// "@", +/// POSTGRES_HOST => "localhost:5432", +/// "/", +/// POSTGRES_DB => "test", +/// ), +/// } +/// +/// fn main() { +/// env::set_var("POSTGRES_USERNAME", "user"); +/// env::set_var("POSTGRES_PASSWORD", "pass"); +/// +/// config::init(); +/// assert_eq!(config::DATABASE_URL(), "postgres://user:pass@localhost:5432/test".to_string()); +/// } +/// ``` +/// +/// Concatinated variables can be only strings and support all features like namespaces and meta. +/// +/// ```rust +/// # use itconfig::config; +/// config! { +/// CONCATED_NAMESPACE { +/// #[env_name = "DATABASE_URL"] +/// CONCAT_ENVVAR < ( +/// "postgres://", +/// NOT_DEFINED_PG_USERNAME => "user", +/// ":", +/// NOT_DEFINED_PG_PASSWORD => "pass", +/// "@", +/// NOT_DEFINED_PG_HOST => "localhost:5432", +/// "/", +/// NOT_DEFINED_PG_DB => "test", +/// ), +/// } +/// } +/// # fn main() { config::init () } +/// ``` +/// +/// Static +/// ------ +/// +/// Starting with 0.11 version you can use lazy static for improve speed of variable. This is very +/// useful, if you use variable more than once. +/// +/// ```rust +/// # use itconfig::config; +/// # use std::env; +/// config! { +/// static APP_BASE_URL => "/api", +/// } +/// +/// fn main () { +/// env::set_var("APP_BASE_URL", "/api/v1"); +/// +/// config::init(); +/// assert_eq!(config::APP_BASE_URL(), "/api/v1"); +/// } +/// ``` +/// +/// You also can use static with concat variables +/// +/// ```rust +/// # use itconfig::config; +/// config! { +/// static CONNECTION_STRING < ( +/// "postgres://", +/// NOT_DEFINED_PG_USERNAME => "user", +/// ":", +/// NOT_DEFINED_PG_PASSWORD => "pass", +/// "@", +/// NOT_DEFINED_PG_HOST => "localhost:5432", +/// "/", +/// NOT_DEFINED_PG_DB => "test", +/// ), +/// } +/// +/// fn main () { +/// config::init(); +/// assert_eq!(config::CONNECTION_STRING(), "postgres://user:pass@localhost:5432/test".to_string()); +/// } +/// ``` +/// +/// +/// --- +/// +/// This module will also contain helper method: +/// -------------------------------------------- +/// +/// ```rust +/// pub fn init() {} +/// ``` +/// +/// Run this at the main function for check all required variables without default value. +/// +/// Panics +/// ------ +/// +/// If you miss some required variables your application will panic at startup. +/// +/// Examples +/// -------- +/// +/// ```rust +/// # use itconfig::config; +/// // use dotenv::dotenv; +/// +/// config! { +/// DEBUG: bool => true, +/// HOST: String => "127.0.0.1", +/// } +/// +/// fn main () { +/// // dotenv().ok(); +/// config::init(); +/// assert_eq!(config::HOST(), String::from("127.0.0.1")); +/// } +/// ``` +/// +#[proc_macro] +pub fn config(input: TokenStream) -> TokenStream { + let namespace = parse_macro_input!(input as RootNamespace); + namespace.into_token_stream().into() +} \ No newline at end of file diff --git a/itconfig-macro/src/parse.rs b/itconfig-macro/src/parse.rs new file mode 100644 index 0000000..821c89e --- /dev/null +++ b/itconfig-macro/src/parse.rs @@ -0,0 +1,262 @@ +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 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") { + 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) -> 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(Some(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 = 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 = 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.clone())) + .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")? + }; + + 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(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 { + is_static, + name, + ty, + initial, + concat_parts, + env_name: None, + meta: vec![], + }) + } +} diff --git a/itconfig-tests/Cargo.toml b/itconfig-tests/Cargo.toml index fbc6ede..7a72838 100644 --- a/itconfig-tests/Cargo.toml +++ b/itconfig-tests/Cargo.toml @@ -9,7 +9,7 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -itconfig = { path = '../itconfig' } +itconfig = { path = '../itconfig', features = ["macro"] } criterion = "0.3.1" lazy_static = "1.4.0" diff --git a/itconfig-tests/benches/main_benches.rs b/itconfig-tests/benches/main_benches.rs index 56c9193..5c9de99 100644 --- a/itconfig-tests/benches/main_benches.rs +++ b/itconfig-tests/benches/main_benches.rs @@ -59,22 +59,22 @@ fn source_macro_vs_lazy_macro(c: &mut Criterion) { let source = Fun::new("source", |b, _| { b.iter(move || { - assert_eq!(cfg::TEST(), "test"); + assert_eq!(config::TEST(), "test"); }) }); let lazy = Fun::new("lazy", |b, _| { b.iter(move || { - assert_eq!(cfg::LAZY_TEST(), "test"); + assert_eq!(config::LAZY_TEST(), "test"); }) }); let source_with_default = Fun::new("source_with_default", |b, _| { b.iter(move || { - assert_eq!(cfg::TEST_WITH_DEFAULT(), "default"); + assert_eq!(config::TEST_WITH_DEFAULT(), "default"); }) }); let lazy_with_default = Fun::new("lazy_with_default", |b, _| { b.iter(move || { - assert_eq!(cfg::LAZY_TEST_WITH_DEFAULT(), "default"); + assert_eq!(config::LAZY_TEST_WITH_DEFAULT(), "default"); }) }); diff --git a/itconfig-tests/tests/config_macro.rs b/itconfig-tests/tests/config_macro.rs index 0507387..2aa8145 100644 --- a/itconfig-tests/tests/config_macro.rs +++ b/itconfig-tests/tests/config_macro.rs @@ -1,118 +1,114 @@ -use std::env; -use std::env::VarError; - -#[macro_use] -extern crate itconfig; - - -#[test] -#[should_panic(expected = "Environment variable \"MISS_VARIABLE\" is missing")] -fn should_panic_if_miss_env_variable() { - config! { +mod test_case_1 { + itconfig::config! { MISS_VARIABLE: bool, } - cfg::init(); + #[test] + #[should_panic(expected = "Environment variable \"MISS_VARIABLE\" is missing")] + fn should_panic_if_miss_env_variable() { + config::init(); + } } -#[test] -fn one_variable() { - env::set_var("DEBUG", "t"); +mod test_case_2 { + use std::env; - config! { + itconfig::config! { DEBUG: bool, } - cfg::init(); - assert_eq!(cfg::DEBUG(), true); - env::remove_var("DEBUG"); + #[test] + fn one_variable() { + env::set_var("DEBUG", "t"); + + config::init(); + assert_eq!(config::DEBUG(), true); + env::remove_var("DEBUG"); + } } -#[test] -fn one_variable_with_default_value() { - config! { + +mod test_case_3 { + itconfig::config! { DEBUG: bool => true, } - cfg::init(); - assert_eq!(cfg::DEBUG(), true); + #[test] + fn one_variable_with_default_value() { + config::init(); + assert_eq!(config::DEBUG(), true); + } } -#[test] -fn few_variables_with_default_value() { - config! { +mod test_case_4 { + itconfig::config! { FOO: bool => true, BAR: bool => false, } - cfg::init(); - assert_eq!(cfg::FOO(), true); - assert_eq!(cfg::BAR(), false); + #[test] + fn few_variables_with_default_value() { + config::init(); + assert_eq!(config::FOO(), true); + assert_eq!(config::BAR(), false); + } } -#[test] -fn different_types_with_default_value() { - config! { +mod test_case_5 { + itconfig::config! { NUMBER: i32 => 30, BOOL: bool => true, STR: String => "str", STRING: String => "string".to_string(), } - cfg::init(); - assert_eq!(cfg::NUMBER(), 30); - assert_eq!(cfg::BOOL(), true); - assert_eq!(cfg::STR(), "str".to_string()); - assert_eq!(cfg::STRING(), "string".to_string()); + #[test] + fn different_types_with_default_value() { + config::init(); + assert_eq!(config::NUMBER(), 30); + assert_eq!(config::BOOL(), true); + assert_eq!(config::STR(), "str".to_string()); + assert_eq!(config::STRING(), "string".to_string()); + } } -#[test] -fn convert_bool_type_value_from_env() { - env::set_var("T_BOOL", "t"); - env::set_var("TRUE_BOOL", "true"); - env::set_var("NUM_BOOL", "1"); - env::set_var("ON_BOOL", "on"); - env::set_var("CAMEL_CASE", "True"); - env::set_var("FALSE_BOOL", "false"); +mod test_case_6 { + use std::env; - config! { + itconfig::config! { T_BOOL: bool, TRUE_BOOL: bool, NUM_BOOL: bool, ON_BOOL: bool, CAMEL_CASE: bool, FALSE_BOOL: bool, - } - cfg::init(); - assert_eq!(cfg::T_BOOL(), true); - assert_eq!(cfg::TRUE_BOOL(), true); - assert_eq!(cfg::NUM_BOOL(), true); - assert_eq!(cfg::ON_BOOL(), true); - assert_eq!(cfg::CAMEL_CASE(), true); - assert_eq!(cfg::FALSE_BOOL(), false); + #[test] + fn convert_bool_type_value_from_env() { + env::set_var("T_BOOL", "t"); + env::set_var("TRUE_BOOL", "true"); + env::set_var("NUM_BOOL", "1"); + env::set_var("ON_BOOL", "on"); + env::set_var("CAMEL_CASE", "True"); + env::set_var("FALSE_BOOL", "false"); + + config::init(); + assert_eq!(config::T_BOOL(), true); + assert_eq!(config::TRUE_BOOL(), true); + assert_eq!(config::NUM_BOOL(), true); + assert_eq!(config::ON_BOOL(), true); + assert_eq!(config::CAMEL_CASE(), true); + assert_eq!(config::FALSE_BOOL(), false); + } } -#[test] -fn convert_number_type_value_from_env() { - env::set_var("I8", "10"); - env::set_var("I16", "10"); - env::set_var("I32", "10"); - env::set_var("I64", "10"); - env::set_var("I128", "10"); - env::set_var("ISIZE","10"); - env::set_var("U8", "10"); - env::set_var("U16", "10"); - env::set_var("U32", "10"); - env::set_var("U64", "10"); - env::set_var("U128", "10"); - env::set_var("USIZE","10"); - env::set_var("F32", "10"); - env::set_var("F64","10"); - config! { +mod test_case_7 { + use std::env; + + itconfig::config! { I8: i8, I16: i16, I32: i32, @@ -129,40 +125,59 @@ fn convert_number_type_value_from_env() { F64: f64, } - cfg::init(); - assert_eq!(cfg::I8(), 10); - assert_eq!(cfg::I16(), 10); - assert_eq!(cfg::I32(), 10); - assert_eq!(cfg::I64(), 10); - assert_eq!(cfg::ISIZE(), 10); - assert_eq!(cfg::U8(), 10); - assert_eq!(cfg::U16(), 10); - assert_eq!(cfg::U32(), 10); - assert_eq!(cfg::U64(), 10); - assert_eq!(cfg::USIZE(), 10); - assert_eq!(cfg::F32(), 10.0); - assert_eq!(cfg::F64(), 10.0); + #[test] + fn convert_number_type_value_from_env() { + env::set_var("I8", "10"); + env::set_var("I16", "10"); + env::set_var("I32", "10"); + env::set_var("I64", "10"); + env::set_var("I128", "10"); + env::set_var("ISIZE", "10"); + env::set_var("U8", "10"); + env::set_var("U16", "10"); + env::set_var("U32", "10"); + env::set_var("U64", "10"); + env::set_var("U128", "10"); + env::set_var("USIZE", "10"); + env::set_var("F32", "10"); + env::set_var("F64", "10"); + + config::init(); + assert_eq!(config::I8(), 10); + assert_eq!(config::I16(), 10); + assert_eq!(config::I32(), 10); + assert_eq!(config::I64(), 10); + assert_eq!(config::ISIZE(), 10); + assert_eq!(config::U8(), 10); + assert_eq!(config::U16(), 10); + assert_eq!(config::U32(), 10); + assert_eq!(config::U64(), 10); + assert_eq!(config::USIZE(), 10); + assert_eq!(config::F32(), 10.0); + assert_eq!(config::F64(), 10.0); + } } -#[test] -fn change_configuration_module_name() { - config! { - #![mod_name = custom_config_name] +mod test_case_8 { + itconfig::config! { + #![config(name = "custom_config_name")] DEBUG: bool => true, } - custom_config_name::init(); - assert_eq!(custom_config_name::DEBUG(), true); + #[test] + fn change_configuration_module_name() { + custom_config_name::init(); + assert_eq!(custom_config_name::DEBUG(), true); + } } -#[test] -fn configuration_with_namespace() { - env::set_var("DB_HOST", "t"); +mod test_case_9 { + use std::env; - config! { + itconfig::config! { DEBUG: bool => true, DB { @@ -174,14 +189,18 @@ fn configuration_with_namespace() { APP {} } - cfg::init(); - assert_eq!(cfg::DEBUG(), true); - assert_eq!(cfg::DB::HOST(), true); + #[test] + fn configuration_with_namespace() { + env::set_var("DB_HOST", "t"); + + config::init(); + assert_eq!(config::DEBUG(), true); + assert_eq!(config::DB::HOST(), true); + } } -#[test] -fn configuration_with_nested_namespaces() { - config! { +mod test_case_10 { + itconfig::config! { FIRST { SECOND { THIRD { @@ -191,14 +210,16 @@ fn configuration_with_nested_namespaces() { } } - cfg::init(); - assert_eq!(cfg::FIRST::SECOND::THIRD::FOO(), 50); + #[test] + fn configuration_with_nested_namespaces() { + config::init(); + assert_eq!(config::FIRST::SECOND::THIRD::FOO(), 50); + } } -#[cfg(feature = "meta_namespace")] -#[test] -fn configuration_namespaces_with_custom_meta() { - config! { + +mod test_case_11 { + itconfig::config! { FIRST { #[cfg(feature = "meta_namespace")] SECOND { @@ -209,16 +230,19 @@ fn configuration_namespaces_with_custom_meta() { } } - cfg::init(); - assert_eq!(cfg::FIRST::SECOND::THIRD::FOO(), 50); + #[cfg(feature = "meta_namespace")] + #[test] + fn configuration_namespaces_with_custom_meta() { + config::init(); + assert_eq!(config::FIRST::SECOND::THIRD::FOO(), 50); + } } -#[test] -fn configuration_variables_and_namespace_in_lowercase() { - env::set_var("TESTING", "t"); - env::set_var("NAMESPACE_FOO", "t"); - config! { +mod test_case_12 { + use std::env; + + itconfig::config! { testing: bool, namespace { @@ -226,17 +250,22 @@ fn configuration_variables_and_namespace_in_lowercase() { } } - cfg::init(); - assert_eq!(cfg::testing(), true); - assert_eq!(cfg::namespace::foo(), true); + #[test] + fn configuration_variables_and_namespace_in_lowercase() { + env::set_var("TESTING", "t"); + env::set_var("NAMESPACE_FOO", "t"); + + config::init(); + assert_eq!(config::testing(), true); + assert_eq!(config::namespace::foo(), true); + } } -#[test] -fn custom_environment_name_for_variable() { - env::set_var("MY_CUSTOM_NAME", "95"); +mod test_case_13 { + use std::env; - config! { + itconfig::config! { #[env_name = "MY_CUSTOM_NAME"] PER_PAGE: i32, @@ -246,16 +275,21 @@ fn custom_environment_name_for_variable() { } } - cfg::init(); - assert_eq!(cfg::PER_PAGE(), 95); - assert_eq!(cfg::APP::RECIPES_PER_PAGE(), 95); + #[test] + fn custom_environment_name_for_variable() { + env::set_var("MY_CUSTOM_NAME", "95"); + + config::init(); + assert_eq!(config::PER_PAGE(), 95); + assert_eq!(config::APP::RECIPES_PER_PAGE(), 95); + } } -#[test] -fn stranger_meta_data() { - env::set_var("MY_CUSTOM_NAME", "95"); - config! { +mod test_case_14 { + use std::env; + + itconfig::config! { #[cfg(feature = "postgres")] #[env_name = "MY_CUSTOM_NAME"] DATABASE_URL: String, @@ -265,40 +299,47 @@ fn stranger_meta_data() { DATABASE_URL: i32, } - cfg::init(); - #[cfg(not(feature = "postgres"))] - assert_eq!(cfg::DATABASE_URL(), 95); + #[test] + fn stranger_meta_data() { + env::set_var("MY_CUSTOM_NAME", "95"); - #[cfg(feature = "postgres")] - assert_eq!(cfg::DATABASE_URL(), "95"); + config::init(); + #[cfg(not(feature = "postgres"))] + assert_eq!(config::DATABASE_URL(), 95); + + #[cfg(feature = "postgres")] + assert_eq!(config::DATABASE_URL(), "95"); + } } -#[test] -fn setting_default_env_variable() { - config! { + +mod test_case_15 { + use std::env; + + itconfig::config! { DEFAULT_ENV_STRING: String => "localhost", DEFAULT_ENV_BOOLEAN: bool => true, DEFAULT_ENV_UINT: u32 => 40, DEFAULT_ENV_FLOAT: f64 => 40.9, } - cfg::init(); - assert_eq!(env::var("DEFAULT_ENV_STRING"), Ok("localhost".to_string())); - assert_eq!(env::var("DEFAULT_ENV_BOOLEAN"), Ok("true".to_string())); - assert_eq!(env::var("DEFAULT_ENV_UINT"), Ok("40".to_string())); - assert_eq!(env::var("DEFAULT_ENV_FLOAT"), Ok("40.9".to_string())); + #[test] + fn setting_default_env_variable() { + config::init(); + + assert_eq!(env::var("DEFAULT_ENV_STRING"), Ok("localhost".to_string())); + assert_eq!(env::var("DEFAULT_ENV_BOOLEAN"), Ok("true".to_string())); + assert_eq!(env::var("DEFAULT_ENV_UINT"), Ok("40".to_string())); + assert_eq!(env::var("DEFAULT_ENV_FLOAT"), Ok("40.9".to_string())); + } } -#[test] -fn concatenate_environment_variables() { - env::set_var("POSTGRES_USERNAME", "user"); - env::set_var("POSTGRES_PASSWORD", "pass"); - env::set_var("POSTGRES_HOST", "localhost"); - env::set_var("POSTGRES_DB", "test"); +mod test_case_16 { + use std::env; - config! { + itconfig::config! { DATABASE_URL < ( "postgres://", POSTGRES_USERNAME, @@ -311,16 +352,23 @@ fn concatenate_environment_variables() { ), } - cfg::init(); - assert_eq!(cfg::DATABASE_URL(), String::from("postgres://user:pass@localhost/test")); + #[test] + fn concatenate_environment_variables() { + env::set_var("POSTGRES_USERNAME", "user"); + env::set_var("POSTGRES_PASSWORD", "pass"); + env::set_var("POSTGRES_HOST", "localhost"); + env::set_var("POSTGRES_DB", "test"); + + config::init(); + assert_eq!(config::DATABASE_URL(), String::from("postgres://user:pass@localhost/test")); + } } -#[test] -fn setting_default_concat_env_variable() { - env::set_var("SETTING_DEFAULT_CONCAT_ENV_VARIABLE", "custom"); +mod test_case_17 { + use std::env; - config! { + itconfig::config! { DEFAULT_CONCAT_ENV < ( "string", "/", @@ -328,15 +376,19 @@ fn setting_default_concat_env_variable() { ), } - cfg::init(); - assert_eq!(env::var("DEFAULT_CONCAT_ENV"), Ok("string/custom".to_string())); + + #[test] + fn setting_default_concat_env_variable() { + env::set_var("SETTING_DEFAULT_CONCAT_ENV_VARIABLE", "custom"); + + config::init(); + assert_eq!(env::var("DEFAULT_CONCAT_ENV"), Ok("string/custom".to_string())); + } } -#[test] -#[should_panic(expected = "Environment variable \"PG_USERNAME\" is missing")] -fn concatenate_not_defined_environment_variables() { - config! { +mod test_case_18 { + itconfig::config! { DATABASE_URL < ( "postgres://", PG_USERNAME, @@ -348,13 +400,19 @@ fn concatenate_not_defined_environment_variables() { PG_DB, ), } - cfg::init(); + + #[test] + #[should_panic(expected = "Environment variable \"PG_USERNAME\" is missing")] + fn concatenate_not_defined_environment_variables() { + config::init(); + } } -#[test] -fn default_value_for_concatenate_env_parameter() { - config! { +mod test_case_19 { + use std::env; + + itconfig::config! { CONCATENATED_DATABASE_URL < ( "postgres://", NOT_DEFINED_PG_USERNAME => "user", @@ -367,16 +425,23 @@ fn default_value_for_concatenate_env_parameter() { ), } - cfg::init(); - assert_eq!( - env::var("CONCATENATED_DATABASE_URL"), - Ok("postgres://user:pass@localhost:5432/test".to_string()) - ); + + #[test] + fn default_value_for_concatenate_env_parameter() { + config::init(); + assert_eq!( + env::var("CONCATENATED_DATABASE_URL"), + Ok("postgres://user:pass@localhost:5432/test".to_string()) + ); + } } -#[test] -fn envname_meta_for_concatenated_env_variable() { - config! { + +mod test_case_20 { + use std::env; + use std::env::VarError; + + itconfig::config! { #[env_name = "CUSTOM_CONCAT_ENVNAME"] CONCAT_ENVVAR < ( "postgres://", @@ -390,17 +455,23 @@ fn envname_meta_for_concatenated_env_variable() { ), } - cfg::init(); - assert_eq!( - env::var("CUSTOM_CONCAT_ENVNAME"), - Ok("postgres://user:pass@localhost:5432/test".to_string()) - ); - assert_eq!(env::var("CONCAT_ENVVAR"), Err(VarError::NotPresent)); + #[test] + fn envname_meta_for_concatenated_env_variable() { + config::init(); + assert_eq!( + env::var("CUSTOM_CONCAT_ENVNAME"), + Ok("postgres://user:pass@localhost:5432/test".to_string()) + ); + assert_eq!(env::var("CONCAT_ENVVAR"), Err(VarError::NotPresent)); + } } -#[test] -fn concatenated_environment_variable_in_namespace() { - config! { + +mod test_case_21 { + use std::env; + use std::env::VarError; + + itconfig::config! { CONCATED_NAMESPACE { CONCAT_ENVVAR < ( "postgres://", @@ -415,19 +486,21 @@ fn concatenated_environment_variable_in_namespace() { } } - cfg::init(); - assert_eq!( - env::var("CONCATED_NAMESPACE_CONCAT_ENVVAR"), - Ok("postgres://user:pass@localhost:5432/test".to_string()) - ); - assert_eq!(env::var("CONCAT_ENVVAR"), Err(VarError::NotPresent)); + + #[test] + fn concatenated_environment_variable_in_namespace() { + config::init(); + assert_eq!( + env::var("CONCATED_NAMESPACE_CONCAT_ENVVAR"), + Ok("postgres://user:pass@localhost:5432/test".to_string()) + ); + assert_eq!(env::var("CONCAT_ENVVAR"), Err(VarError::NotPresent)); + } } -#[test] -#[cfg(feature = "static")] -fn static_variables() { - config! { +mod test_case_22 { + itconfig::config! { static STATIC_STR => "test", static STATIC_STRING: String => "test", static STATIC_I8: i8 => 1, @@ -450,23 +523,27 @@ fn static_variables() { ), } - cfg::init(); - assert_eq!(cfg::STATIC_STR(), "test"); - assert_eq!(cfg::STATIC_STRING(), "test".to_string()); - assert_eq!(cfg::STATIC_I8(), 1); - assert_eq!(cfg::STATIC_I16(), 1); - assert_eq!(cfg::STATIC_I32(), 1); - assert_eq!(cfg::STATIC_I64(), 1); - assert_eq!(cfg::STATIC_I128(), 1); - assert_eq!(cfg::STATIC_ISIZE(), 1); - assert_eq!(cfg::STATIC_U8(), 1); - assert_eq!(cfg::STATIC_U16(), 1); - assert_eq!(cfg::STATIC_U32(), 1); - assert_eq!(cfg::STATIC_U64(), 1); - assert_eq!(cfg::STATIC_U128(), 1); - assert_eq!(cfg::STATIC_USIZE(), 1); - assert_eq!(cfg::STATIC_F32(), 1.0); - assert_eq!(cfg::STATIC_F64(), 1.0); - assert_eq!(cfg::STATIC_CONCAT_VARIABLE(), "static part".to_string()) + #[test] + fn static_variables() { + config::init(); + + assert_eq!(config::STATIC_STR(), "test"); + assert_eq!(config::STATIC_STRING(), "test".to_string()); + assert_eq!(config::STATIC_I8(), 1); + assert_eq!(config::STATIC_I16(), 1); + assert_eq!(config::STATIC_I32(), 1); + assert_eq!(config::STATIC_I64(), 1); + assert_eq!(config::STATIC_I128(), 1); + assert_eq!(config::STATIC_ISIZE(), 1); + assert_eq!(config::STATIC_U8(), 1); + assert_eq!(config::STATIC_U16(), 1); + assert_eq!(config::STATIC_U32(), 1); + assert_eq!(config::STATIC_U64(), 1); + assert_eq!(config::STATIC_U128(), 1); + assert_eq!(config::STATIC_USIZE(), 1); + assert_eq!(config::STATIC_F32(), 1.0); + assert_eq!(config::STATIC_F64(), 1.0); + assert_eq!(config::STATIC_CONCAT_VARIABLE(), "static part".to_string()) + } } diff --git a/itconfig/Cargo.toml b/itconfig/Cargo.toml index 7476318..d10caa9 100644 --- a/itconfig/Cargo.toml +++ b/itconfig/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itconfig" -version = "0.11.2" +version = "1.0.0" authors = ["Dmitriy Pleshevskiy "] description = "Easy build a configs from environment variables and use it in globally." categories = ["config", "web-programming"] @@ -15,15 +15,17 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -failure = { version = "0.1.6", features = ["derive"]} -lazy_static = { version = "1.4.0", optional = true } +failure = { version = "0.1.7", features = ["derive"]} serde_json = { version = "1.0.44", optional = true } +itconfig-macro = { path = "../itconfig-macro", optional = true } + +[dev-dependencies] +lazy_static = "1.4.0" [features] -default = ["macro", "primitives", "static"] +default = ["primitives"] -macro = [] -static = ["lazy_static"] +macro = ["itconfig-macro"] array = ["serde_json"] diff --git a/itconfig/README.md b/itconfig/README.md index 3e46d6d..2a3015d 100644 --- a/itconfig/README.md +++ b/itconfig/README.md @@ -5,10 +5,29 @@ Easy build a configs from environment variables and use it in globally. We recommend you start with the [documentation]. +## Installation + +These macros require a Rust compiler version 1.31 or newer. + +Add `itconfig = { version = "1.0", features = ["macro"] }` as a dependency in `Cargo.toml`. + +`Cargo.toml` example: + +```toml +[package] +name = "my-crate" +version = "0.1.0" +authors = ["Me "] + +[dependencies] +itconfig = { version = "1.0", features = ["macro"] } +``` + + ## Example usage ```rust -#[macro_use] extern crate itconfig; +use std::itconfig; use std::env; //use dotenv::dotenv; @@ -56,11 +75,11 @@ fn main () { // dotenv().ok(); env::set_var("FEATURE_NEW_MENU", "t"); - cfg::init(); - assert_eq!(cfg::HOST(), String::from("127.0.0.1")); - assert_eq!(cfg::DATABASE_URL(), String::from("postgres://user:pass@localhost:5432/test")); - assert_eq!(cfg::APP:ARTICLE:PER_PAGE(), 15); - assert_eq!(cfg::FEATURE::NEW_MENU(), true); + config::init(); + assert_eq!(config::HOST(), String::from("127.0.0.1")); + assert_eq!(config::DATABASE_URL(), String::from("postgres://user:pass@localhost:5432/test")); + assert_eq!(config::APP:ARTICLE:PER_PAGE(), 15); + assert_eq!(config::FEATURE::NEW_MENU(), true); } ``` @@ -93,6 +112,7 @@ fn main() { * [x] Add nested namespaces * [x] Support meta for namespaces * [x] Support array type +* [x] Rewrite to proc macro * [ ] Support hashmap type * [ ] Support custom env type * [ ] Common configuration for namespace variables @@ -102,7 +122,6 @@ fn main() { * **default** - ["macro", "primitives", "static"] * **macro** - Activates `config!` macros for easy configure web application. -* **static** - Add `static` option to `config!` macros (uses optional `lazy_static` package). * **array** - Add EnvString impl for vector type (uses optional `serde_json` package). * **primitives** - Group for features: `numbers` and `bool`. * **numbers** - Group for features: `int`, `uint` and `float`. diff --git a/itconfig/src/cfg.rs b/itconfig/src/cfg.rs new file mode 100644 index 0000000..7931749 --- /dev/null +++ b/itconfig/src/cfg.rs @@ -0,0 +1,33 @@ +config! { + #![config(unwrap)] + + TEST => "hello", + static MAIN => "main", + TEST2 => "test", + + CONCAT < ( + TESTTTT => "hellooooooo", + " ", + "world", + ), + + NAMESPACE { + TEST: String => "test", + + NAMESPACE { + TEST: &'static str => "test", + + #[env_prefix = "HELLO_"] + NAMESPACE { + TEST: &'static str => "test", + + #[cfg(not(target_os = "linux"))] + #[env_prefix = "WORLD_"] + NAMESPACE { + #[env_name = "TEST_TEST_TEST"] + TEST: &'static str => "test", + } + } + } + } +} diff --git a/itconfig/src/lib.rs b/itconfig/src/lib.rs index 39c2b9e..f2d608d 100644 --- a/itconfig/src/lib.rs +++ b/itconfig/src/lib.rs @@ -12,10 +12,30 @@ //! as application lifetime. Because of it I decided to create my own library. //! //! +//! ## Installation +//! +//! These macros require a Rust compiler version 1.31 or newer. +//! +//! Add `itconfig = { version = "1.0", features = ["macro"] }` as a dependency in `Cargo.toml`. +//! +//! +//! `Cargo.toml` example: +//! +//! ```toml +//! [package] +//! name = "my-crate" +//! version = "0.1.0" +//! authors = ["Me "] +//! +//! [dependencies] +//! itconfig = { version = "1.0", features = ["macro"] } +//! ``` +//! +//! //! ## Example usage //! //! ```rust -//! #[macro_use] extern crate itconfig; +//! use itconfig::config; //! use std::env; //! // use dotenv::dotenv; //! @@ -61,12 +81,12 @@ //! // dotenv().ok(); //! env::set_var("FEATURE_NEW_MENU", "t"); //! -//! cfg::init(); -//! assert_eq!(cfg::HOST(), String::from("127.0.0.1")); -//! assert_eq!(cfg::DATABASE_URL(), String::from("postgres://user:pass@localhost:5432/test")); -//! assert_eq!(cfg::APP::BASE_URL(), "/api"); -//! assert_eq!(cfg::APP::ARTICLE::PER_PAGE(), 15); -//! assert_eq!(cfg::FEATURE::NEW_MENU(), true); +//! config::init(); +//! assert_eq!(config::HOST(), String::from("127.0.0.1")); +//! assert_eq!(config::DATABASE_URL(), String::from("postgres://user:pass@localhost:5432/test")); +//! assert_eq!(config::APP::BASE_URL(), "/api"); +//! assert_eq!(config::APP::ARTICLE::PER_PAGE(), 15); +//! assert_eq!(config::FEATURE::NEW_MENU(), true); //! } //! ``` //! @@ -89,9 +109,8 @@ //! //! ## Available features //! -//! * **default** - ["macro", "primitives", "static"] +//! * **default** - ["primitives"] //! * **macro** - Activates `config!` macros for easy configure web application. -//! * **static** - Add `static` option to `config!` macros (uses optional `lazy_static` package). //! * **array** - Add EnvString impl for vector type (uses optional `serde_json` package). //! * **primitives** - Group for features: `numbers` and `bool`. //! * **numbers** - Group for features: `int`, `uint` and `float`. @@ -129,8 +148,6 @@ #[macro_use] extern crate failure; -#[cfg(feature = "static")] -pub extern crate lazy_static; mod enverr; mod getenv; @@ -146,9 +163,7 @@ pub mod prelude { #[cfg(feature = "macro")] -//#[allow(unused_imports)] -#[macro_use] -mod r#macro; +extern crate itconfig_macro; #[cfg(feature = "macro")] #[doc(hidden)] -pub use r#macro::*; \ No newline at end of file +pub use itconfig_macro::*; \ No newline at end of file diff --git a/itconfig/src/macro.rs b/itconfig/src/macro.rs deleted file mode 100644 index 9ba8a2f..0000000 --- a/itconfig/src/macro.rs +++ /dev/null @@ -1,880 +0,0 @@ - -/// ### _This API requires the following crate features to be activated: `macro`_ -/// -/// Creates new public mod with function fo get each environment variable of mapping. -/// -/// All variables are required and program will panic if some variables haven't value, but you -/// can add default value for specific variable. -/// -/// Starts with v0.6.0 if you don't have an optional variable, the variable is set automatically. -/// -/// Example usage -/// ------------- -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// # use std::env; -/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db"); -/// config! { -/// DATABASE_URL: String, -/// } -/// # cfg::init() -/// ``` -/// -/// Config with default value -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// # use std::env; -/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db"); -/// config! { -/// DATABASE_URL: String, -/// HOST: String => "127.0.0.1", -/// } -/// # cfg::init() -/// ``` -/// -/// By default itconfig lib creates module with 'cfg' name. But you can use simple meta instruction -/// if you want to rename module. In the example below we renamed module to 'configuration' -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// # use std::env; -/// env::set_var("DEBUG", "t"); -/// -/// config! { -/// #![mod_name = configuration] -/// -/// DEBUG: bool, -/// } -/// -/// configuration::init(); -/// assert_eq!(configuration::DEBUG(), true); -/// ``` -/// -/// Namespaces -/// ---------- -/// -/// You can use namespaces for env variables -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// # use std::env; -/// env::set_var("DEBUG", "t"); -/// env::set_var("DATABASE_USERNAME", "user"); -/// env::set_var("DATABASE_PASSWORD", "pass"); -/// env::set_var("DATABASE_HOST", "localhost"); -/// -/// config! { -/// DEBUG: bool, -/// DATABASE { -/// USERNAME: String, -/// PASSWORD: String, -/// HOST: String, -/// } -/// } -/// # cfg::init() -/// ``` -/// -/// Now you can use nested structure in namespaces without limits :) -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// config! { -/// FIRST { -/// SECOND { -/// THIRD { -/// FOO: bool => true, -/// } -/// } -/// } -/// } -/// # cfg::init(); -/// ``` -/// -/// Namespaces supports custom meta -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// config! { -/// #[cfg(feature = "first")] -/// FIRST { -/// #[cfg(feature = "second")] -/// SECOND { -/// #[cfg(feature = "third")] -/// THIRD { -/// FOO: bool => true, -/// } -/// } -/// } -/// } -/// # cfg::init(); -/// ``` -/// -/// Meta -/// ---- -/// -/// If you want to read custom env name for variable you can change it manually. -/// -/// **A variable in the nameespace will lose environment prefix** -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// # use std::env; -/// env::set_var("MY_CUSTOM_NAME", "95"); -/// -/// config! { -/// #[env_name = "MY_CUSTOM_NAME"] -/// PER_PAGE: i32, -/// -/// APP { -/// #[env_name = "MY_CUSTOM_NAME"] -/// RECIPES_PER_PAGE: i32, -/// } -/// } -/// -/// cfg::init(); -/// assert_eq!(cfg::PER_PAGE(), 95); -/// assert_eq!(cfg::APP::RECIPES_PER_PAGE(), 95); -/// ``` -/// -/// Also you can add custom meta for each variable. For example feature configurations. -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// config! { -/// #[cfg(feature = "postgres")] -/// DATABASE_URL: String, -/// -/// #[cfg(not(feature = "postgres"))] -/// DATABASE_URL: String, -/// } -/// # fn main() {} -/// ``` -/// -/// Concatenate -/// ----------- -/// -/// Try to concatenate env variable or strings or both to you env variable. It's easy! -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// # use std::env; -/// env::set_var("POSTGRES_USERNAME", "user"); -/// env::set_var("POSTGRES_PASSWORD", "pass"); -/// -/// config! { -/// DATABASE_URL < ( -/// "postgres://", -/// POSTGRES_USERNAME, -/// ":", -/// POSTGRES_PASSWORD, -/// "@", -/// POSTGRES_HOST => "localhost:5432", -/// "/", -/// POSTGRES_DB => "test", -/// ), -/// } -/// -/// cfg::init(); -/// assert_eq!(cfg::DATABASE_URL(), "postgres://user:pass@localhost:5432/test".to_string()) -/// ``` -/// -/// Concatinated variables can be only strings and support all features like namespaces and meta. -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// config! { -/// CONCATED_NAMESPACE { -/// #[env_name = "DATABASE_URL"] -/// CONCAT_ENVVAR < ( -/// "postgres://", -/// NOT_DEFINED_PG_USERNAME => "user", -/// ":", -/// NOT_DEFINED_PG_PASSWORD => "pass", -/// "@", -/// NOT_DEFINED_PG_HOST => "localhost:5432", -/// "/", -/// NOT_DEFINED_PG_DB => "test", -/// ), -/// } -/// } -/// -/// cfg::init(); -/// ``` -/// -/// Static -/// ------ -/// -/// `with feauter = "static"` -/// -/// Starting with 0.11 version you can use lazy static for improve speed of variable. This is very -/// useful, if you use variable more than once. -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// # use std::env; -/// env::set_var("APP_BASE_URL", "/api/v1"); -/// -/// config! { -/// static APP_BASE_URL => "/api", -/// } -/// -/// cfg::init(); -/// assert_eq!(cfg::APP_BASE_URL(), "/api/v1"); -/// ``` -/// -/// You also can use static with concat variables -/// -/// ```rust -/// # #[macro_use] extern crate itconfig; -/// config! { -/// static CONNECTION_STRING < ( -/// "postgres://", -/// NOT_DEFINED_PG_USERNAME => "user", -/// ":", -/// NOT_DEFINED_PG_PASSWORD => "pass", -/// "@", -/// NOT_DEFINED_PG_HOST => "localhost:5432", -/// "/", -/// NOT_DEFINED_PG_DB => "test", -/// ), -/// } -/// -/// cfg::init(); -/// assert_eq!(cfg::CONNECTION_STRING(), "postgres://user:pass@localhost:5432/test".to_string()); -/// ``` -/// -/// -/// --- -/// -/// This module will also contain helper method: -/// -------------------------------------------- -/// -/// ```rust -/// pub fn init() {} -/// ``` -/// -/// Run this at the main function for check all required variables without default value. -/// -/// Panics -/// ------ -/// -/// If you miss some required variables your application will panic at startup. -/// -/// Examples -/// -------- -/// -/// ```rust -/// #[macro_use] extern crate itconfig; -/// // use dotenv::dotenv; -/// -/// config! { -/// DEBUG: bool => true, -/// HOST: String => "127.0.0.1", -/// } -/// -/// fn main () { -/// // dotenv().ok(); -/// cfg::init(); -/// assert_eq!(cfg::HOST(), String::from("127.0.0.1")); -/// } -/// ``` -/// -#[macro_export(local_inner_macros)] -macro_rules! config { - ($($tokens:tt)*) => { - __itconfig_parse_module! { - tokens = [$($tokens)*], - name = cfg, - } - } -} - - -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_invalid_syntax { - () => { - compile_error!( - "Invalid `config!` syntax. Please see the `config!` macro docs for more info.\ - `https://docs.rs/itconfig/latest/itconfig/macro.config.html`" - ); - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_parse_module { - // Find module name - ( - tokens = [ - #![mod_name = $mod_name:ident] - $($rest:tt)* - ], - name = $ignore:tt, - ) => { - __itconfig_parse_module! { - tokens = [$($rest)*], - name = $mod_name, - } - }; - - // Done parsing module - ( - tokens = $tokens:tt, - name = $name:tt, - ) => { - __itconfig_parse_variables! { - tokens = $tokens, - variables = [], - namespaces = [], - module = { - env_prefix = "", - name = $name, - meta = [], - }, - } - }; - - // Invalid syntax - ($($tokens:tt)*) => { - __itconfig_invalid_syntax!(); - }; -} - - -#[cfg(feature = "static")] -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_impl_static_feature { - (@import_modules) => { - use $crate::lazy_static::lazy_static; - }; - - ( - unparsed_meta = $meta:tt, - unparsed_concat = $concat:tt, - name = $name:ident, - ty = $ty:ty, - $(default = $default:expr,)? - tokens = $tokens:tt, - args = [$($args:tt)*], - ) => { - __itconfig_parse_variables! { - current_variable = { - unparsed_meta = $meta, - meta = [], - unparsed_concat = $concat, - concat = [], - name = $name, - ty = $ty, - is_static = true, - $(default = $default,)? - }, - tokens = $tokens, - $($args)* - } - }; -} - - -#[cfg(not(feature = "static"))] -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_impl_static_feature { - (@import_modules) => { }; - - ($($tt:tt)*) => { - compile_error!( - "Feature `static` is required for enable this macro function.\ - Please see the `config!` macro docs for more info.\ - `https://docs.rs/itconfig/latest/itconfig/macro.config.html`" - ); - }; -} - - -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_get_ty_or_default { - () => { &'static str }; - ($ty:ty) => { $ty }; -} - - -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_parse_variables { - // Find namespace - ( - tokens = [ - $(#$meta:tt)* - $ns_name:ident { $($ns_tokens:tt)* } - $($rest:tt)* - ], - $($args:tt)* - ) => { - __itconfig_parse_variables! { - tokens = [$($ns_tokens)*], - variables = [], - namespaces = [], - module = { - env_prefix = concat!(stringify!($ns_name), "_"), - name = $ns_name, - meta = [$(#$meta)*], - }, - callback = { - tokens = [$($rest)*], - $($args)* - }, - } - }; - - // Find static concatenated variable - ( - tokens = [ - $(#$meta:tt)* - static $name:ident < ($($inner:tt)+), - $($rest:tt)* - ], - $($args:tt)* - ) => { - __itconfig_impl_static_feature! { - unparsed_meta = [$(#$meta)*], - unparsed_concat = [$($inner)+], - name = $name, - ty = String, - tokens = [$($rest)*], - args = [$($args)*], - } - }; - - // Find concatenated variable - ( - tokens = [ - $(#$meta:tt)* - $name:ident < ($($inner:tt)+), - $($rest:tt)* - ], - $($args:tt)* - ) => { - __itconfig_parse_variables! { - current_variable = { - unparsed_meta = [$(#$meta)*], - meta = [], - unparsed_concat = [$($inner)+], - concat = [], - name = $name, - ty = String, - is_static = false, - }, - tokens = [$($rest)*], - $($args)* - } - }; - - // Find static variable - ( - tokens = [ - $(#$meta:tt)* - static $name:ident $(: $ty:ty)? $(=> $default:expr)?, - $($rest:tt)* - ], - $($args:tt)* - ) => { - __itconfig_impl_static_feature! { - unparsed_meta = [$(#$meta)*], - unparsed_concat = [], - name = $name, - ty = __itconfig_get_ty_or_default!($($ty)?), - $(default = $default,)? - tokens = [$($rest)*], - args = [$($args)*], - } - }; - - // Find variable - ( - tokens = [ - $(#$meta:tt)* - $name:ident $(: $ty:ty)? $(=> $default:expr)?, - $($rest:tt)* - ], - $($args:tt)* - ) => { - __itconfig_parse_variables! { - current_variable = { - unparsed_meta = [$(#$meta)*], - meta = [], - unparsed_concat = [], - concat = [], - name = $name, - ty = __itconfig_get_ty_or_default!($($ty)?), - is_static = false, - $(default = $default,)? - }, - tokens = [$($rest)*], - $($args)* - } - }; - - // Find meta with custom env name - ( - current_variable = { - unparsed_meta = [ - #[env_name = $env_name:expr] - $($rest:tt)* - ], - meta = $meta:tt, - unparsed_concat = $unparsed_concat:tt, - concat = $concat:tt, - name = $name:ident, - $($current_variable:tt)* - }, - $($args:tt)* - ) => { - __itconfig_parse_variables! { - current_variable = { - unparsed_meta = [$($rest)*], - meta = $meta, - unparsed_concat = $unparsed_concat, - concat = $concat, - name = $name, - env_name = $env_name, - $($current_variable)* - }, - $($args)* - } - }; - - // Find stranger meta - ( - current_variable = { - unparsed_meta = [ - #$stranger_meta:tt - $($rest:tt)* - ], - meta = [$(#$meta:tt,)*], - $($current_variable:tt)* - }, - $($args:tt)* - ) => { - __itconfig_parse_variables! { - current_variable = { - unparsed_meta = [$($rest)*], - meta = [$(#$meta,)* #$stranger_meta,], - $($current_variable)* - }, - $($args)* - } - }; - - // Parse concat params - ( - current_variable = { - unparsed_meta = $unparsed_meta:tt, - meta = $meta:tt, - unparsed_concat = [ - $concat_param:tt$( => $default:expr)?, - $($rest:tt)* - ], - concat = [$($concat:expr,)*], - $($current_variable:tt)* - }, - $($args:tt)* - ) => { - __itconfig_parse_variables! { - current_variable = { - unparsed_meta = $unparsed_meta, - meta = $meta, - unparsed_concat = [$($rest)*], - concat = [$($concat,)* __itconfig_concat_param!($concat_param$( => $default)?),], - $($current_variable)* - }, - $($args)* - } - }; - - // Done parsing variable - ( - current_variable = { - unparsed_meta = [], - meta = $meta:tt, - unparsed_concat = [], - $($current_variable:tt)* - }, - tokens = $tokens:tt, - variables = [$($variables:tt,)*], - $($args:tt)* - ) => { - __itconfig_parse_variables! { - tokens = $tokens, - variables = [$($variables,)* { meta = $meta, $($current_variable)* },], - $($args)* - } - }; - - // Done parsing all variables of namespace - ( - tokens = [], - variables = $ns_variables:tt, - namespaces = $ns_namespaces:tt, - module = $ns_module:tt, - callback = { - tokens = $tokens:tt, - variables = $variables:tt, - namespaces = [$($namespaces:tt,)*], - $($args:tt)* - }, - ) => { - __itconfig_parse_variables! { - tokens = $tokens, - variables = $variables, - namespaces = [ - $($namespaces,)* - { - variables = $ns_variables, - namespaces = $ns_namespaces, - module = $ns_module, - }, - ], - $($args)* - } - }; - - // Done parsing all variables - ( - tokens = [], - $($args:tt)* - ) => { - __itconfig_impl_namespace!($($args)*); - }; - - // Invalid syntax - ($($tokens:tt)*) => { - __itconfig_invalid_syntax!(); - }; -} - - -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_impl_namespace { - ( - variables = [$({ - meta = $var_meta:tt, - concat = $var_concat:tt, - name = $var_name:ident, - $(env_name = $env_name:expr,)? - ty = $ty:ty, - is_static = $is_static:ident, - $($variable:tt)* - },)*], - namespaces = [$({ - variables = $ns_variable:tt, - namespaces = $ns_namespaces:tt, - module = { - env_prefix = $ns_env_prefix:expr, - name = $ns_mod_name:ident, - meta = [$(#$ns_meta:tt)*], - }, - },)*], - module = { - env_prefix = $env_prefix:expr, - name = $mod_name:ident, - meta = [$(#$meta:tt)*], - }, - ) => { - $(#$meta)* - pub mod $mod_name { - #![allow(non_snake_case)] - - __itconfig_impl_static_feature!( @import_modules ); - - $(__itconfig_impl_namespace! { - variables = $ns_variable, - namespaces = $ns_namespaces, - module = { - env_prefix = $ns_env_prefix, - name = $ns_mod_name, - meta = [$(#$ns_meta)*], - }, - })* - - pub fn init() { - $($var_name();)* - $( - $(#$ns_meta)* - $ns_mod_name::init(); - )* - } - - $(__itconfig_variable! { - meta = $var_meta, - concat = $var_concat, - name = $var_name, - env_prefix = $env_prefix, - $(env_name = $env_name,)? - ty = $ty, - is_static = $is_static, - $($variable)* - })* - } - }; - - // Invalid syntax - ($($tokens:tt)*) => { - __itconfig_invalid_syntax!(); - }; -} - - -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_concat_param { - // Find env parameter with default value - ($env_name:ident => $default:expr) => ( - itconfig::get_env_or_default( - stringify!($env_name).to_uppercase().as_str(), - $default - ) - ); - - // Find env parameter without default value - ($env_name:ident) => ( - itconfig::get_env_or_panic(stringify!($env_name).to_uppercase().as_str()) - ); - - // Find string parameter - ($str:expr) => ( $str.to_string() ); - - // Invalid syntax - ($($tokens:tt)*) => { - __itconfig_invalid_syntax!(); - }; -} - - -#[macro_export] -#[doc(hidden)] -macro_rules! __itconfig_variable { - // Set default env name - ( - meta = $meta:tt, - concat = $concat:tt, - name = $name:ident, - env_prefix = $env_prefix:expr, - ty = $ty:ty, - $($args:tt)* - ) => { - __itconfig_variable! { - meta = $meta, - concat = $concat, - name = $name, - env_prefix = $env_prefix, - env_name = concat!($env_prefix, stringify!($name)).to_uppercase(), - ty = $ty, - $($args)* - } - }; - - // Add method for env variable - ( - meta = $meta:tt, - concat = $concat:tt, - name = $name:ident, - env_prefix = $env_prefix:expr, - env_name = $env_name:expr, - ty = $ty:ty, - is_static = $is_static:ident, - $(default = $default:expr,)? - ) => { - __itconfig_variable!( - @wrap - is_static = $is_static, - meta = $meta, - name = $name, - ty = $ty, - value = __itconfig_variable!( - @inner - concat = $concat, - env_name = $env_name, - $(default = $default,)? - ), - ); - }; - - // Wrap static variables - ( - @wrap - is_static = true, - meta = [$(#$meta:tt,)*], - name = $name:ident, - ty = $ty:ty, - value = $value:expr, - ) => ( - $(#$meta)* - pub fn $name() -> $ty { - lazy_static! { - static ref $name: $ty = $value; - } - - (*$name).clone() - } - ); - - // Wrap functions - ( - @wrap - is_static = false, - meta = [$(#$meta:tt,)*], - name = $name:ident, - ty = $ty:ty, - value = $value:expr, - ) => ( - $(#$meta)* - pub fn $name() -> $ty { $value } - ); - - // Concatenate function body - ( - @inner - concat = [$($concat:expr,)+], - env_name = $env_name:expr, - $($args:tt)* - ) => ({ - let value_parts: Vec = vec!($($concat),+); - let value = value_parts.join(""); - std::env::set_var($env_name, value.as_str()); - value - }); - - // Env without default - ( - @inner - concat = [], - env_name = $env_name:expr, - ) => ( - itconfig::get_env_or_panic($env_name.to_string().as_str()) - ); - - // Env with default - ( - @inner - concat = [], - env_name = $env_name:expr, - default = $default:expr, - ) => ( - itconfig::get_env_or_set_default( - $env_name.to_string().as_str(), - $default - ) - ); - - // Invalid syntax - ($($tokens:tt)*) => { - __itconfig_invalid_syntax!(); - }; -} -