1
Fork 0
mirror of https://github.com/Steffo99/micronfig.git synced 2024-11-25 09:34:19 +00:00

Complete the new crate, but...

This commit is contained in:
Steffo 2023-12-30 03:31:15 +01:00
parent c6df6cb3af
commit ef0094cd2c
Signed by: steffo
GPG key ID: 2A24051445686895
7 changed files with 259 additions and 9 deletions

View file

@ -17,15 +17,13 @@ rustdoc-args = ["--document-private-items", "--cfg", "docsrs"]
[features]
default = ["single_envvars", "single_envfiles", "multi", "handle", "macros"]
single_envvars = []
single_envfiles = []
multi = ["single_envvars", "single_envfiles"]
handle = ["multi"]
macros = ["lazy_static", "handle"]
default = ["envvars", "envfiles", "envdot"]
envvars = []
envfiles = []
envdot = ["regex"]
testing = ["tempfile"]
[dependencies]
lazy_static = { version = "1.4.0", optional = true }
tempfile = { version = "3.5.0", optional = true }
tempfile = { version = "3.9.0", optional = true }
regex = { version = "1.10.2", optional = true }

22
src/cache.rs Normal file
View file

@ -0,0 +1,22 @@
use std::fmt::Debug;
use std::path::Path;
#[derive(Clone, Default, Debug)]
pub struct MicronfigCache {
#[cfg(feature = "envdot")]
pub dotenvs: Vec<crate::sources::envdot::DotEnv>
}
impl MicronfigCache {
#[cfg(feature = "envdot")]
pub fn add_envdot<P>(&mut self, path: P)
where P: AsRef<Path> + Debug
{
self.dotenvs.push(
crate::sources::envdot::DotEnv::from(
path
)
);
}
}

143
src/lib.rs Normal file
View file

@ -0,0 +1,143 @@
use std::fmt::Debug;
pub mod sources;
pub mod cache;
#[macro_export]
macro_rules! __micronfig_last {
[ $head:ty, $( $tail:ty, )+ ] => {
$crate::__micronfig_last![ $( $tail, )+ ]
};
[ $head:ty, ] => {
$head
}
}
/// # Examples
///
/// ## Get a string directly
///
/// ```
/// micronfig::config! {
/// MY_STRING_A: String,
/// MY_STRING_B: String
/// }
/// ```
///
/// ## Parse envvar as a number
///
/// ```
/// micronfig::config! {
/// MY_UNSIGNED_NUMBER: u32,
/// MY_SIGNED_NUMBER: i32
/// }
/// ```
///
/// ## Parse string as an IpAddr
///
/// ```
/// use std::net::IpAddr;
///
/// micronfig::config! {
/// MY_IP_ADDR: IpAddr
/// }
/// ```
///
/// ## Parse string as a number, then convert it into an Ipv4Addr
///
/// ```
/// use std::net::Ipv4Addr;
///
/// micronfig::config! {
/// MY_NUMERIC_IP_ADDR: Ipv4Addr
/// }
/// ```
///
/// ## Parse string with custom logic resulting into a number
///
/// ```
/// struct CustomConverter(u8);
///
/// impl From<String> for CustomConverter {
/// fn from(value: String) -> Self {
/// Self(123)
/// }
/// }
///
/// impl Into<u8> for CustomConverter {
/// fn into(self) -> u8 {
/// self.0
/// }
/// }
///
/// micronfig::config! {
/// MY_ONETWOTHREE: CustomConverter => u8,
/// MY_ONETWOTHREE_BUT_EXPLICIT: String => CustomConverter => u8
/// }
/// ```
///
#[macro_export]
macro_rules! config {
{ $( $identifier:ident: $( $conversion:ty )=>* ),+ } => {
static __micronfig_cache: std::sync::OnceLock<$crate::cache::MicronfigCache> = std::sync::OnceLock::new();
fn __micronfig_init_cache() -> $crate::cache::MicronfigCache {
let mut this = $crate::cache::MicronfigCache::default();
if cfg!(feature = "envdot") {
this.add_envdot("./.env");
this.add_envdot("./.env.local");
}
this
}
fn __micronfig_get(key: &str) -> Option<String> {
let mut value: Option<String> = None;
if cfg!(feature = "envfiles") && value.is_none() {
value = $crate::sources::envfiles::get(format!("{key}_FILE"));
}
if cfg!(feature = "envvars") && value.is_none() {
value = $crate::sources::envvars::get(&key);
}
if cfg!(feature = "envdot") && value.is_none() {
let cache = __micronfig_cache.get_or_init(__micronfig_init_cache);
for dotenv in cache.dotenvs.iter() {
value = $crate::sources::envdot::get(dotenv, &key);
if value.is_some() {
break;
}
}
}
value
}
$(
pub(self) mod $identifier {
pub static lock: std::sync::OnceLock<Option< $crate::__micronfig_last![ $( $conversion, )+ ] >> = std::sync::OnceLock::new();
}
pub(crate) fn $identifier () -> &'static Option< $crate::__micronfig_last![ $( $conversion, )+ ] > {
$identifier::lock.get_or_init(|| {
let key = stringify!($identifier);
let value = __micronfig_get(key);
$(
let value: Option<$conversion> = value.map(Into::into);
)+
value
})
}
)+
}
}
config! {
SOMETHING: String
}

50
src/sources/envdot.rs Normal file
View file

@ -0,0 +1,50 @@
//! Variables defined in specific dotenv files.
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use regex::Regex;
#[derive(Clone, Default, PartialEq, Eq, Debug)]
pub struct DotEnv(
HashMap<String, String>
);
impl DotEnv {
pub fn var(&self, key: &str) -> Option<&String> {
self.0.get(key)
}
}
impl<P> From<P> for DotEnv
where P: AsRef<Path> + Debug
{
fn from(value: P) -> Self {
let mut file = File::open(&value)
.expect(&*format!("to be able to open {value:?}"));
let mut contents: String = String::new();
file.read_to_string(&mut contents)
.expect(&*format!("to be able to read {value:?}"));
let mut keys: HashMap<String, String> = HashMap::new();
let re = Regex::new(r#"^(?:export\s+)?([^=]+)\s*=\s*(.+)$"#)
.expect("Regex to be valid");
let _ = contents.split("\n")
.filter_map(|line| re.captures(line))
.map(|capture| (capture[0].to_owned(), capture[1].to_owned()))
.map(|(key, value)| keys.insert(key, value));
Self(keys)
}
}
/// Get the contents of the file at the path specified by the given environment variable.
pub fn get(dotenv: &DotEnv, key: &str) -> Option<String>
{
dotenv.var(key).map(|v| v.to_owned())
}

21
src/sources/envfiles.rs Normal file
View file

@ -0,0 +1,21 @@
//! Contents of files at paths defined by environment variables.
/// Get the contents of the file at the path specified by the given environment variable.
pub fn get<Key>(key: Key) -> Option<String>
where Key: AsRef<std::ffi::OsStr>,
{
let path = std::env::var(key).ok()?;
let path = std::ffi::OsString::from(path);
let path = std::path::PathBuf::from(path);
let mut file = std::fs::File::open(&path)
.expect(&*format!("to be able to open file at {path:?}"));
use std::io::Read;
let mut data = String::new();
file.read_to_string(&mut data)
.expect(&*format!("to be able to read from file at {path:?}"));
Some(data)
}

8
src/sources/envvars.rs Normal file
View file

@ -0,0 +1,8 @@
//! Environment variables.
/// Get the specified environment variable.
pub fn get<Key>(key: Key) -> Option<String>
where Key: AsRef<std::ffi::OsStr>,
{
std::env::var(key).ok()
}

8
src/sources/mod.rs Normal file
View file

@ -0,0 +1,8 @@
#[cfg(feature = "envvars")]
pub mod envvars;
#[cfg(feature = "envfiles")]
pub mod envfiles;
#[cfg(feature = "envdot")]
pub mod envdot;