feat: add itconfig proc macro

This commit is contained in:
Dmitriy Pleshevskiy 2020-03-12 23:20:34 +03:00
parent e54edc2c81
commit 3554fe9537
20 changed files with 1258 additions and 1144 deletions

View file

@ -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",
]

View file

@ -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 <user@rust-lang.org>"]
[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`.

View file

@ -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))
}

View file

@ -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);

View file

@ -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"]

View file

@ -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<HttpConnector>) -> HyperResult<Response<Body>> {
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();

View file

@ -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();
}

30
itconfig-macro/Cargo.toml Normal file
View file

@ -0,0 +1,30 @@
[package]
name = "itconfig-macro"
version = "1.0.0"
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
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" }

30
itconfig-macro/src/ast.rs Normal file
View file

@ -0,0 +1,30 @@
use proc_macro2::TokenStream as TokenStream2;
use syn::{Type, Expr, Ident, Attribute};
pub struct RootNamespace {
pub name: Option<Ident>,
pub variables: Vec<Variable>,
pub namespaces: Vec<Namespace>,
pub meta: Vec<Attribute>,
}
pub struct Namespace {
pub name: Ident,
pub variables: Vec<Variable>,
pub namespaces: Vec<Namespace>,
pub env_prefix: Option<String>,
pub meta: Vec<Attribute>,
}
pub struct Variable {
pub is_static: bool,
pub name: Ident,
pub ty: Type,
pub initial: Option<Expr>,
pub concat_parts: Option<Vec<TokenStream2>>,
pub env_name: Option<String>,
pub meta: Vec<Attribute>,
}

View file

@ -0,0 +1,170 @@
use crate::ast::*;
use quote::{quote, ToTokens, TokenStreamExt};
use proc_macro2::TokenStream as TokenStream2;
fn vec_to_token_stream_2<T>(input: &Vec<T>) -> Vec<TokenStream2>
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::<Vec<TokenStream2>>();
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::<Vec<TokenStream2>>();
let inner_meta: Vec<TokenStream2> = 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::<Vec<TokenStream2>>();
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::<Vec<TokenStream2>>();
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<String> = 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
}
));
}
}
}

338
itconfig-macro/src/lib.rs Normal file
View file

@ -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()
}

262
itconfig-macro/src/parse.rs Normal file
View file

@ -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<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![],
})
}
}

View file

@ -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"

View file

@ -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");
})
});

View file

@ -1,74 +1,92 @@
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);
#[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() {
mod test_case_6 {
use std::env;
itconfig::config! {
T_BOOL: bool,
TRUE_BOOL: bool,
NUM_BOOL: bool,
ON_BOOL: bool,
CAMEL_CASE: bool,
FALSE_BOOL: bool,
}
#[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");
@ -76,43 +94,21 @@ fn convert_bool_type_value_from_env() {
env::set_var("CAMEL_CASE", "True");
env::set_var("FALSE_BOOL", "false");
config! {
T_BOOL: bool,
TRUE_BOOL: bool,
NUM_BOOL: bool,
ON_BOOL: bool,
CAMEL_CASE: bool,
FALSE_BOOL: bool,
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);
}
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_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,
}
#[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 stranger_meta_data() {
#[test]
fn custom_environment_name_for_variable() {
env::set_var("MY_CUSTOM_NAME", "95");
config! {
config::init();
assert_eq!(config::PER_PAGE(), 95);
assert_eq!(config::APP::RECIPES_PER_PAGE(), 95);
}
}
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();
#[test]
fn stranger_meta_data() {
env::set_var("MY_CUSTOM_NAME", "95");
config::init();
#[cfg(not(feature = "postgres"))]
assert_eq!(cfg::DATABASE_URL(), 95);
assert_eq!(config::DATABASE_URL(), 95);
#[cfg(feature = "postgres")]
assert_eq!(cfg::DATABASE_URL(), "95");
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();
#[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();
#[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();
#[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();
#[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();
#[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())
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "itconfig"
version = "0.11.2"
version = "1.0.0"
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
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"]

View file

@ -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 <user@rust-lang.org>"]
[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`.

33
itconfig/src/cfg.rs Normal file
View file

@ -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",
}
}
}
}
}

View file

@ -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 <user@rust-lang.org>"]
//!
//! [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::*;
pub use itconfig_macro::*;

View file

@ -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<String> = 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!();
};
}