commit
e398fec00f
7 changed files with 196 additions and 90 deletions
|
@ -1,14 +1,8 @@
|
|||
use crate::ast::*;
|
||||
use crate::utils::{is_option_type, vec_to_token_stream_2};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
|
||||
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;
|
||||
|
@ -131,7 +125,7 @@ impl ToTokens for Variable {
|
|||
let env_name = &self
|
||||
.env_name
|
||||
.clone()
|
||||
.unwrap_or(name.to_string().to_uppercase());
|
||||
.unwrap_or_else(|| name.to_string().to_uppercase());
|
||||
let meta = vec_to_token_stream_2(&self.meta);
|
||||
|
||||
let get_variable: TokenStream2 = if self.concat_parts.is_some() {
|
||||
|
@ -145,6 +139,8 @@ impl ToTokens for Variable {
|
|||
} else if self.initial.is_some() {
|
||||
let initial = self.initial.as_ref().unwrap();
|
||||
quote!(::itconfig::get_env_or_set_default(#env_name, #initial))
|
||||
} else if is_option_type(&self.ty) {
|
||||
quote!(::itconfig::maybe_get_env(#env_name))
|
||||
} else {
|
||||
quote!(::itconfig::get_env_or_panic(#env_name))
|
||||
};
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#![recursion_limit = "256"]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod ast;
|
||||
mod expand;
|
||||
mod parse;
|
||||
mod utils;
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate proc_macro2;
|
||||
|
||||
use self::proc_macro::TokenStream;
|
||||
use ast::RootNamespace;
|
||||
use quote::ToTokens;
|
||||
|
|
|
@ -117,7 +117,7 @@ impl Parse for RootNamespace {
|
|||
match attr.parse_meta()? {
|
||||
Meta::List(MetaList { nested, .. }) => {
|
||||
let message =
|
||||
format!("expected #[config(name = \"...\")] or #[config(unwrap)]");
|
||||
"expected #[config(name = \"...\")] or #[config(unwrap)]".to_string();
|
||||
match nested.first().unwrap() {
|
||||
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
|
@ -127,7 +127,7 @@ impl Parse for RootNamespace {
|
|||
if path.is_ident("name") {
|
||||
name = Some(Ident::new(&lit_str.value(), Span::call_site()));
|
||||
} else {
|
||||
Err(Error::new_spanned(attr, message))?;
|
||||
return Err(Error::new_spanned(attr, message));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(path)) => {
|
||||
|
@ -135,17 +135,17 @@ impl Parse for RootNamespace {
|
|||
name = None;
|
||||
with_module = false;
|
||||
} else {
|
||||
Err(Error::new_spanned(attr, message))?;
|
||||
return Err(Error::new_spanned(attr, message));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
Err(Error::new_spanned(attr, message))?;
|
||||
return Err(Error::new_spanned(attr, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let message = format!("expected #[config(...)]");
|
||||
Err(Error::new_spanned(attr, message))?;
|
||||
let message = "expected #[config(...)]".to_string();
|
||||
return Err(Error::new_spanned(attr, message));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -166,7 +166,7 @@ impl Parse for RootNamespace {
|
|||
let prefix = String::new();
|
||||
let namespaces = namespaces
|
||||
.into_iter()
|
||||
.map(fill_env_prefix(prefix.clone()))
|
||||
.map(fill_env_prefix(prefix))
|
||||
.collect();
|
||||
|
||||
Ok(RootNamespace {
|
||||
|
@ -231,7 +231,10 @@ impl Parse for Variable {
|
|||
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 env_name = &concat_var
|
||||
.env_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| name.to_string());
|
||||
|
||||
let get_variable = if concat_var.initial.is_some() {
|
||||
let initial = concat_var.initial.as_ref().unwrap();
|
||||
|
@ -245,8 +248,10 @@ impl Parse for Variable {
|
|||
let part: Lit = content.parse()?;
|
||||
tmp_vec.push(quote!(#part.to_string()));
|
||||
}
|
||||
|
||||
content.parse::<Comma>().ok();
|
||||
}
|
||||
|
||||
concat_parts = Some(tmp_vec);
|
||||
} else {
|
||||
initial = input
|
||||
|
|
36
itconfig-macro/src/utils.rs
Normal file
36
itconfig-macro/src/utils.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::ToTokens;
|
||||
use syn::{Path, Type};
|
||||
|
||||
const OPTION_PATH_IDENTS: &[&str] = &["Option|", "std|option|Option|", "core|option|Option|"];
|
||||
|
||||
pub fn vec_to_token_stream_2<T>(input: &[T]) -> Vec<TokenStream2>
|
||||
where
|
||||
T: ToTokens,
|
||||
{
|
||||
input.iter().map(|ns| ns.into_token_stream()).collect()
|
||||
}
|
||||
|
||||
fn path_ident(path: &Path) -> String {
|
||||
path.segments
|
||||
.iter()
|
||||
.into_iter()
|
||||
.fold(String::with_capacity(250), |mut acc, v| {
|
||||
acc.push_str(&v.ident.to_string());
|
||||
acc.push('|');
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
fn is_option_path_ident(path_ident: String) -> bool {
|
||||
OPTION_PATH_IDENTS.iter().any(|s| path_ident == *s)
|
||||
}
|
||||
|
||||
pub fn is_option_type(ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Path(ty_path) => {
|
||||
ty_path.qself.is_none() && is_option_path_ident(path_ident(&ty_path.path))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
|
@ -531,3 +531,29 @@ mod test_case_22 {
|
|||
assert_eq!(config::STATIC_CONCAT_VARIABLE(), "static part".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
mod test_case_23 {
|
||||
use std::env;
|
||||
|
||||
itconfig::config! {
|
||||
SOMETHING: Option<&'static str>,
|
||||
#[env_name = "SOMETHING"]
|
||||
STD_SOMETHING: std::option::Option<&'static str>,
|
||||
#[env_name = "SOMETHING"]
|
||||
CORE_SOMETHING: core::option::Option<&'static str>,
|
||||
|
||||
NOTHING: Option<&'static str>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_variables() {
|
||||
config::init();
|
||||
|
||||
env::set_var("SOMETHING", "hello world");
|
||||
|
||||
assert_eq!(config::SOMETHING(), Some("hello world"));
|
||||
assert_eq!(config::STD_SOMETHING(), Some("hello world"));
|
||||
assert_eq!(config::CORE_SOMETHING(), Some("hello world"));
|
||||
assert_eq!(config::NOTHING(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
use itconfig::EnvError::*;
|
||||
use itconfig::*;
|
||||
use std::env;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Environment variable \"TEST_CASE_1\" is missing")]
|
||||
fn get_missing_env() {
|
||||
get_env_or_panic::<String>("TEST_CASE_1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_2\"")]
|
||||
fn get_env_with_invalid_value() {
|
||||
let env_name = "TEST_CASE_2";
|
||||
env::set_var(&env_name, "30r");
|
||||
get_env_or_panic::<u32>(env_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_result_of_missing_env() {
|
||||
let env_name = String::from("TEST_CASE_3");
|
||||
let env_val = get_env::<String>(&env_name);
|
||||
assert_eq!(env_val, Err(MissingVariable(env_name)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_result_of_env_with_invalid_value() {
|
||||
let env_name = String::from("TEST_CASE_4");
|
||||
env::set_var(&env_name, "30r");
|
||||
let env_val = get_env::<u32>(&env_name);
|
||||
assert_eq!(env_val, Err(FailedToParse(env_name)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_result_of_env_successfully() {
|
||||
env::set_var("TEST_CASE_5", "30");
|
||||
let env_var = get_env("TEST_CASE_5");
|
||||
assert_eq!(env_var, Ok(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_missing_env_with_default_value() {
|
||||
let flag: bool = get_env_or_default("TEST_CASE_6", "true");
|
||||
assert_eq!(flag, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_7\"")]
|
||||
fn get_invalid_env_with_default_value() {
|
||||
env::set_var("TEST_CASE_7", "30r");
|
||||
get_env_or_default::<u32, _>("TEST_CASE_7", 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_8\"")]
|
||||
fn get_env_with_invalid_default_value() {
|
||||
get_env_or_default::<u32, _>("TEST_CASE_8", "30r");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_env_with_default_successfully() {
|
||||
env::set_var("TEST_CASE_9", "10");
|
||||
let env_val: u32 = get_env_or_default("TEST_CASE_9", 30);
|
||||
assert_eq!(env_val, 10)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_missing_env_with_set_default_value() {
|
||||
let flag: bool = get_env_or_set_default("TEST_CASE_10", "true");
|
||||
assert_eq!(flag, true);
|
||||
|
||||
let env_var = env::var("TEST_CASE_10");
|
||||
assert_eq!(env_var, Ok(String::from("true")))
|
||||
}
|
|
@ -1,6 +1,34 @@
|
|||
use crate::prelude::*;
|
||||
use std::env;
|
||||
|
||||
/// Same as get_env but returns Option enum instead Result
|
||||
///
|
||||
/// Example
|
||||
/// -------
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate itconfig;
|
||||
/// # use itconfig::maybe_get_env;
|
||||
/// use std::env;
|
||||
///
|
||||
/// fn main () {
|
||||
/// env::set_var("HOST", "https://example.com");
|
||||
///
|
||||
/// let host: Option<&'static str> = maybe_get_env("HOST");
|
||||
/// let not_existence_host: Option<&'static str> = maybe_get_env("NOT_EXISTENCE_HOST");
|
||||
///
|
||||
/// assert_eq!(host, Some("https://example.com"));
|
||||
/// assert_eq!(not_existence_host, None);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub fn maybe_get_env<T>(env_name: &str) -> Option<T>
|
||||
where
|
||||
T: FromEnvString,
|
||||
{
|
||||
get_env(env_name).ok()
|
||||
}
|
||||
|
||||
/// This function is similar as `get_env`, but it unwraps result with panic on error.
|
||||
///
|
||||
/// Panics
|
||||
|
@ -137,3 +165,89 @@ where
|
|||
fn make_panic<T>(e: EnvError) -> T {
|
||||
panic!("{}", e)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Environment variable \"TEST_CASE_1\" is missing")]
|
||||
fn get_missing_env() {
|
||||
get_env_or_panic::<String>("TEST_CASE_1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_2\"")]
|
||||
fn get_env_with_invalid_value() {
|
||||
let env_name = "TEST_CASE_2";
|
||||
env::set_var(&env_name, "30r");
|
||||
get_env_or_panic::<u32>(env_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_result_of_missing_env() {
|
||||
let env_name = String::from("TEST_CASE_3");
|
||||
let env_val = get_env::<String>(&env_name);
|
||||
assert_eq!(env_val, Err(EnvError::MissingVariable(env_name)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_result_of_env_with_invalid_value() {
|
||||
let env_name = String::from("TEST_CASE_4");
|
||||
env::set_var(&env_name, "30r");
|
||||
let env_val = get_env::<u32>(&env_name);
|
||||
assert_eq!(env_val, Err(EnvError::FailedToParse(env_name)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_result_of_env_successfully() {
|
||||
env::set_var("TEST_CASE_5", "30");
|
||||
let env_var = get_env("TEST_CASE_5");
|
||||
assert_eq!(env_var, Ok(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_missing_env_with_default_value() {
|
||||
let flag: bool = get_env_or_default("TEST_CASE_6", "true");
|
||||
assert_eq!(flag, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_7\"")]
|
||||
fn get_invalid_env_with_default_value() {
|
||||
env::set_var("TEST_CASE_7", "30r");
|
||||
get_env_or_default::<u32, _>("TEST_CASE_7", 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_8\"")]
|
||||
fn get_env_with_invalid_default_value() {
|
||||
get_env_or_default::<u32, _>("TEST_CASE_8", "30r");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_env_with_default_successfully() {
|
||||
env::set_var("TEST_CASE_9", "10");
|
||||
let env_val: u32 = get_env_or_default("TEST_CASE_9", 30);
|
||||
assert_eq!(env_val, 10)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_missing_env_with_set_default_value() {
|
||||
let flag: bool = get_env_or_set_default("TEST_CASE_10", "true");
|
||||
assert_eq!(flag, true);
|
||||
|
||||
let env_var = env::var("TEST_CASE_10");
|
||||
assert_eq!(env_var, Ok(String::from("true")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_optional_env() {
|
||||
env::set_var("TEST_CASE_11", "something");
|
||||
let something: Option<&'static str> = maybe_get_env("TEST_CASE_11");
|
||||
assert_eq!(something, Some("something"));
|
||||
|
||||
let nothing: Option<&'static str> = maybe_get_env("TEST_CASE_11_NONE");
|
||||
assert_eq!(nothing, None);
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue