mirror of
https://github.com/Steffo99/micronfig.git
synced 2024-11-21 23:54:20 +00:00
so much stuff...
This commit is contained in:
parent
ef0094cd2c
commit
a6f08368b4
18 changed files with 368 additions and 250 deletions
2
.directory
Normal file
2
.directory
Normal file
|
@ -0,0 +1,2 @@
|
|||
[Desktop Entry]
|
||||
Icon=/home/steffo/Workspaces/Steffo99/micronfig/icon.png
|
|
@ -2,7 +2,9 @@
|
|||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/micronfig.iml" filepath="$PROJECT_DIR$/.idea/micronfig.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/_workspace.iml" filepath="$PROJECT_DIR$/_workspace.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/micronfig/micronfig.iml" filepath="$PROJECT_DIR$/micronfig/micronfig.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/micronfig_macros/micronfig_macros.iml" filepath="$PROJECT_DIR$/micronfig_macros/micronfig_macros.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
34
Cargo.toml
34
Cargo.toml
|
@ -1,29 +1,5 @@
|
|||
[package]
|
||||
name = "micronfig"
|
||||
version = "0.2.0"
|
||||
authors = ["Stefano Pigozzi <me@steffo.eu>"]
|
||||
edition = "2021"
|
||||
description = "Tiny crate for simple configuration management"
|
||||
repository = "https://github.com/Steffo99/micronfig/"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["twelve-factor-app", "configuration", "config", "environment", "envvar"]
|
||||
categories = ["config"]
|
||||
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
cargo-args = ["--bins"]
|
||||
rustdoc-args = ["--document-private-items", "--cfg", "docsrs"]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["envvars", "envfiles", "envdot"]
|
||||
envvars = []
|
||||
envfiles = []
|
||||
envdot = ["regex"]
|
||||
testing = ["tempfile"]
|
||||
|
||||
[dependencies]
|
||||
tempfile = { version = "3.9.0", optional = true }
|
||||
regex = { version = "1.10.2", optional = true }
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"micronfig",
|
||||
"micronfig_macros",
|
||||
]
|
||||
|
|
9
Micronfig.iml
Normal file
9
Micronfig.iml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RUST_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
11
_workspace.iml
Normal file
11
_workspace.iml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RUST_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
24
micronfig/Cargo.toml
Normal file
24
micronfig/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "micronfig"
|
||||
version = "0.3.0"
|
||||
authors = ["Stefano Pigozzi <me@steffo.eu>"]
|
||||
edition = "2021"
|
||||
description = "Macro-based configuration management"
|
||||
repository = "https://github.com/Steffo99/micronfig/"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["twelve-factor-app", "configuration", "config", "environment", "envvar"]
|
||||
categories = ["config"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--document-private-items"]
|
||||
|
||||
[features]
|
||||
default = ["envvars", "envfiles", "envdot"]
|
||||
envvars = []
|
||||
envfiles = []
|
||||
envdot = ["regex"]
|
||||
|
||||
[dependencies]
|
||||
micronfig_macros = { version = "0.3.0", path = "../micronfig_macros" }
|
||||
regex = { version = "1.10.2", optional = true }
|
65
micronfig/src/cache.rs
Normal file
65
micronfig/src/cache.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
//! Definition of [`Cache`].
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Cache initialized only once per config block and used to quickly retrieve configuration values.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Cache {
|
||||
/// `.env` file cache, in order of access priority.
|
||||
///
|
||||
/// More can be added with [`Cache::register_dotenv`].
|
||||
#[cfg(feature = "envdot")]
|
||||
pub envdot: Vec<crate::envdot::DotEnv>
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
/// Initialize a new cache.
|
||||
pub fn new() -> Self {
|
||||
let mut this = Self::default();
|
||||
|
||||
if cfg!(feature = "envdot") {
|
||||
this.register_dotenv("./.env.local");
|
||||
this.register_dotenv("./.env");
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Get a value from the cache.
|
||||
pub fn get<Key>(&self, key: Key) -> Option<String>
|
||||
where Key: AsRef<&std::ffi::OsStr>
|
||||
{
|
||||
let mut value = None;
|
||||
|
||||
if cfg!(feature = "envfiles") {
|
||||
value = crate::envfiles::get(format!("{key}_FILE"));
|
||||
}
|
||||
|
||||
if cfg!(feature = "envvars") && value.is_none() {
|
||||
value = crate::envvars::get(&key);
|
||||
}
|
||||
|
||||
if cfg!(feature = "envdot") && value.is_none() {
|
||||
for dotenv in self.envdot.iter() {
|
||||
value = crate::envdot::get(dotenv, &key);
|
||||
if value.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
/// Register a new `.env` file in the cache.
|
||||
///
|
||||
/// Equivalent to adding an item to [`Cache::envdot`].
|
||||
#[cfg(feature = "envdot")]
|
||||
pub fn register_dotenv<Path>(&mut self, path: Path)
|
||||
where Path: AsRef<std::path::Path> + Debug
|
||||
{
|
||||
self.envdot.push(
|
||||
crate::envdot::DotEnv::from(path)
|
||||
);
|
||||
}
|
||||
}
|
79
micronfig/src/envdot.rs
Normal file
79
micronfig/src/envdot.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
//! Utilities for fetching configuration values defined in specific `.env` files.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use regex::Regex;
|
||||
|
||||
/// The type of a parsed `.env` file.
|
||||
pub type DotEnv = HashMap<OsString, String>;
|
||||
|
||||
/// Parse a `.env` file.
|
||||
///
|
||||
/// ### Warning
|
||||
///
|
||||
/// This method isn't properly
|
||||
fn parse_dotenv<P>(value: P) -> DotEnv
|
||||
where P: AsRef<Path> + Debug
|
||||
{
|
||||
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<OsString, 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| {
|
||||
let key = &capture[0];
|
||||
let value = &capture[1];
|
||||
|
||||
if value.starts_with('\'') && value.ends_with('\'') {
|
||||
(
|
||||
key.into(),
|
||||
value
|
||||
.strip_prefix('\'')
|
||||
.expect("apostrophe to be prefixed to the value")
|
||||
.strip_suffix('\'')
|
||||
.expect("apostrophe to be suffixed to the value")
|
||||
.to_owned()
|
||||
)
|
||||
}
|
||||
else if value.starts_with('"') && value.ends_with('"') {
|
||||
(
|
||||
key.into(),
|
||||
value
|
||||
.strip_prefix('"')
|
||||
.expect("quotes to be prefixed to the value")
|
||||
.strip_suffix('"')
|
||||
.expect("quotes to be suffixed to the value")
|
||||
.to_owned()
|
||||
)
|
||||
}
|
||||
else {
|
||||
(
|
||||
key.into(),
|
||||
value.to_owned()
|
||||
)
|
||||
}
|
||||
})
|
||||
.map(|(key, value)| keys.insert(key, value));
|
||||
|
||||
keys
|
||||
}
|
||||
|
||||
/// Get the requested variable from a [`DotEnv`] structure.
|
||||
pub fn get<Key>(dotenv: &DotEnv, key: Key) -> Option<String>
|
||||
where Key: AsRef<&std::ffi::OsStr>
|
||||
{
|
||||
dotenv.var(key).map(|v| v.to_owned())
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
//! Contents of files at paths defined by environment variables.
|
||||
|
||||
/// Get the contents of the file at the path specified by the given environment variable.
|
||||
use std::io::Read;
|
||||
|
||||
/// Get the contents of the file at the path specified by the requested environment variable plus `_FILE`.
|
||||
pub fn get<Key>(key: Key) -> Option<String>
|
||||
where Key: AsRef<std::ffi::OsStr>,
|
||||
{
|
||||
let path = std::env::var(key).ok()?;
|
||||
let path = std::env::var(format!("{key}_FILE")).ok()?;
|
||||
|
||||
let path = std::ffi::OsString::from(path);
|
||||
let path = std::path::PathBuf::from(path);
|
||||
|
@ -12,7 +14,6 @@ pub fn get<Key>(key: Key) -> Option<String>
|
|||
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:?}"));
|
|
@ -1,8 +1,8 @@
|
|||
pub mod cache;
|
||||
|
||||
#[cfg(feature = "envvars")]
|
||||
pub mod envvars;
|
||||
|
||||
#[cfg(feature = "envfiles")]
|
||||
pub mod envfiles;
|
||||
|
||||
#[cfg(feature = "envdot")]
|
||||
pub mod envdot;
|
21
micronfig_macros/Cargo.toml
Normal file
21
micronfig_macros/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "micronfig_macros"
|
||||
version = "0.3.0"
|
||||
authors = ["Stefano Pigozzi <me@steffo.eu>"]
|
||||
edition = "2021"
|
||||
description = "Macros for micronfig"
|
||||
repository = "https://github.com/Steffo99/micronfig/"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["twelve-factor-app", "configuration", "config", "environment", "envvar"]
|
||||
categories = ["config"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--document-private-items"]
|
||||
|
||||
[dependencies]
|
||||
syn = "2.0"
|
||||
quote = "1.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
14
micronfig_macros/micronfig_macros.iml
Normal file
14
micronfig_macros/micronfig_macros.iml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="RUST_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
129
micronfig_macros/src/lib.rs
Normal file
129
micronfig_macros/src/lib.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{Ident, parse_macro_input, Token, Type};
|
||||
use syn::punctuated::{Pair, Punctuated};
|
||||
|
||||
|
||||
type Config = Punctuated<Config, Token![,]>;
|
||||
|
||||
struct ConfigItem {
|
||||
identifier: Ident,
|
||||
types: ConfigTypes,
|
||||
}
|
||||
|
||||
type ConfigTypes = Punctuated<Type, Conversion>;
|
||||
|
||||
enum Conversion {
|
||||
From,
|
||||
TryFrom,
|
||||
FromStr,
|
||||
}
|
||||
|
||||
|
||||
impl Parse for ConfigItem {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let identifier = input.parse::<Ident>()?;
|
||||
let types = input.parse::<Punctuated<Type, Conversion>>()?;
|
||||
Ok(Self { identifier, types })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Conversion {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if input.parse::<Token![->]>().is_ok() {
|
||||
Ok(Conversion::From)
|
||||
}
|
||||
else if input.parse::<Token![=>]>().is_ok() {
|
||||
Ok(Conversion::TryFrom)
|
||||
}
|
||||
else if input.parse::<Token![>]>().is_ok() {
|
||||
Ok(Conversion::FromStr)
|
||||
}
|
||||
else {
|
||||
Err(input.error("Cannot determine conversion method to use; valid conversion tokens are `->` (From), `=>` (TryFrom) and `>` (FromStr)."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn config(input: TokenStream) -> TokenStream {
|
||||
let input: Config = parse_macro_input!(input as Config);
|
||||
|
||||
let cache = quote! {
|
||||
mod _cache {
|
||||
pub static lock: std::sync::OnceLock<Cache> = std::sync::OnceLock::new();
|
||||
}
|
||||
|
||||
pub(self) fn _cache() {
|
||||
_cache::lock.get_or_init(micronfig::cache::Cache::new)
|
||||
}
|
||||
};
|
||||
|
||||
let items = input.iter().map(|item: ConfigItem| {
|
||||
let identifier = item.identifier;
|
||||
|
||||
// TODO: Can types be zero-length?
|
||||
|
||||
let mut conversion_code = quote! {};
|
||||
|
||||
let mut previous_conversion: Option<&Conversion> = None;
|
||||
|
||||
for pair in item.types.pairs().into_iter() {
|
||||
if let Some(some_conversion) = previous_conversion {
|
||||
todo!();
|
||||
|
||||
match pair {
|
||||
Pair::Punctuated(target_type, new_conversion) => {
|
||||
previous_conversion = Some(new_conversion);
|
||||
}
|
||||
Pair::End(target_type) => {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
conversion_code = match some_conversion {
|
||||
Conversion::From => quote! {
|
||||
#conversion_code
|
||||
let value: #target_type = value.into();
|
||||
}
|
||||
Conversion::TryFrom => quote! {
|
||||
#conversion_code
|
||||
let value: #target_type = value.try_into()
|
||||
.expect("to be able to convert {}", stringify!(#identifier));
|
||||
}
|
||||
Conversion::FromStr => quote! {
|
||||
#conversion_code
|
||||
let value: #target_type = value.parse()
|
||||
.expect("to be able to parse {}", stringify!(#identifier));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let last_type = item.types.last();
|
||||
|
||||
quote! {
|
||||
mod #identifier {
|
||||
pub(super) lock: std::sync::OnceLock<#last_type> = std::sync::OnceLock::new();
|
||||
}
|
||||
|
||||
pub(crate) fn #identifier() {
|
||||
#identifier::lock.get_or_init(|| {
|
||||
let key = stringify!(#identifier);
|
||||
let value = _cache().get(&key);
|
||||
|
||||
#conversion_code
|
||||
|
||||
value
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#cache
|
||||
#items
|
||||
}
|
||||
}
|
22
src/cache.rs
22
src/cache.rs
|
@ -1,22 +0,0 @@
|
|||
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
143
src/lib.rs
|
@ -1,143 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
//! 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())
|
||||
}
|
Loading…
Reference in a new issue