mirror of
https://github.com/Steffo99/micronfig.git
synced 2024-11-22 08:04:20 +00:00
Complete the new crate, but...
This commit is contained in:
parent
c6df6cb3af
commit
ef0094cd2c
7 changed files with 259 additions and 9 deletions
16
Cargo.toml
16
Cargo.toml
|
@ -17,15 +17,13 @@ rustdoc-args = ["--document-private-items", "--cfg", "docsrs"]
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["single_envvars", "single_envfiles", "multi", "handle", "macros"]
|
default = ["envvars", "envfiles", "envdot"]
|
||||||
single_envvars = []
|
envvars = []
|
||||||
single_envfiles = []
|
envfiles = []
|
||||||
multi = ["single_envvars", "single_envfiles"]
|
envdot = ["regex"]
|
||||||
handle = ["multi"]
|
|
||||||
macros = ["lazy_static", "handle"]
|
|
||||||
testing = ["tempfile"]
|
testing = ["tempfile"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = { version = "1.4.0", optional = true }
|
tempfile = { version = "3.9.0", optional = true }
|
||||||
tempfile = { version = "3.5.0", optional = true }
|
regex = { version = "1.10.2", optional = true }
|
||||||
|
|
||||||
|
|
22
src/cache.rs
Normal file
22
src/cache.rs
Normal 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
143
src/lib.rs
Normal 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
50
src/sources/envdot.rs
Normal 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
21
src/sources/envfiles.rs
Normal 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
8
src/sources/envvars.rs
Normal 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
8
src/sources/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#[cfg(feature = "envvars")]
|
||||||
|
pub mod envvars;
|
||||||
|
|
||||||
|
#[cfg(feature = "envfiles")]
|
||||||
|
pub mod envfiles;
|
||||||
|
|
||||||
|
#[cfg(feature = "envdot")]
|
||||||
|
pub mod envdot;
|
Loading…
Reference in a new issue