mirror of
https://github.com/Steffo99/micronfig.git
synced 2024-11-25 01:24:18 +00:00
It workssssssssss
This commit is contained in:
parent
e2a1726626
commit
b98d2da4e9
11 changed files with 146 additions and 89 deletions
|
@ -2,6 +2,8 @@
|
|||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="RsFunctionNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
|
||||
<inspection_tool class="RsModuleNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
|
||||
<inspection_tool class="SillyAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Definition of [`Cache`].
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Cache initialized only once per config block and used to quickly retrieve configuration values.
|
||||
|
@ -26,22 +27,21 @@ impl Cache {
|
|||
}
|
||||
|
||||
/// Get a value from the cache.
|
||||
pub fn get<Key>(&self, key: Key) -> Option<String>
|
||||
where Key: AsRef<&std::ffi::OsStr>
|
||||
pub fn get(&self, key: &OsStr) -> Option<String>
|
||||
{
|
||||
let mut value = None;
|
||||
|
||||
if cfg!(feature = "envfiles") {
|
||||
value = crate::envfiles::get(format!("{key}_FILE"));
|
||||
value = crate::envfiles::get(key);
|
||||
}
|
||||
|
||||
if cfg!(feature = "envvars") && value.is_none() {
|
||||
value = crate::envvars::get(&key);
|
||||
value = crate::envvars::get(key);
|
||||
}
|
||||
|
||||
if cfg!(feature = "envdot") && value.is_none() {
|
||||
for dotenv in self.envdot.iter() {
|
||||
value = crate::envdot::get(dotenv, &key);
|
||||
value = crate::envdot::get(dotenv, key);
|
||||
if value.is_some() {
|
||||
break;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ impl Cache {
|
|||
where Path: AsRef<std::path::Path> + Debug
|
||||
{
|
||||
self.envdot.push(
|
||||
crate::envdot::DotEnv::from(path)
|
||||
crate::envdot::parse_dotenv(path)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Utilities for fetching configuration values defined in specific `.env` files.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
@ -12,11 +12,7 @@ use regex::Regex;
|
|||
pub type DotEnv = HashMap<OsString, String>;
|
||||
|
||||
/// Parse a `.env` file.
|
||||
///
|
||||
/// ### Warning
|
||||
///
|
||||
/// This method isn't properly
|
||||
fn parse_dotenv<P>(value: P) -> DotEnv
|
||||
pub fn parse_dotenv<P>(value: P) -> DotEnv
|
||||
where P: AsRef<Path> + Debug
|
||||
{
|
||||
let mut file = File::open(&value)
|
||||
|
@ -72,8 +68,7 @@ fn parse_dotenv<P>(value: P) -> DotEnv
|
|||
}
|
||||
|
||||
/// Get the requested variable from a [`DotEnv`] structure.
|
||||
pub fn get<Key>(dotenv: &DotEnv, key: Key) -> Option<String>
|
||||
where Key: AsRef<&std::ffi::OsStr>
|
||||
pub fn get(dotenv: &DotEnv, key: &OsStr) -> Option<String>
|
||||
{
|
||||
dotenv.var(key).map(|v| v.to_owned())
|
||||
dotenv.get(key).map(|v| v.to_owned())
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
//! Contents of files at paths defined by environment variables.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
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(format!("{key}_FILE")).ok()?;
|
||||
pub fn get(key: &OsStr) -> Option<String> {
|
||||
let mut key: std::ffi::OsString = key.to_os_string();
|
||||
key.push("_FILE");
|
||||
let path = std::env::var(key).ok()?;
|
||||
|
||||
let path = std::ffi::OsString::from(path);
|
||||
let path = std::path::PathBuf::from(path);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! Environment variables.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
|
||||
/// Get the specified environment variable.
|
||||
pub fn get<Key>(key: Key) -> Option<String>
|
||||
where Key: AsRef<std::ffi::OsStr>,
|
||||
{
|
||||
pub fn get(key: &OsStr) -> Option<String> {
|
||||
std::env::var(key).ok()
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ syn = "2.0"
|
|||
quote = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
micronfig = { version = "0.3.0", path = "../micronfig" }
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{Ident, parse_macro_input, Token, Type};
|
||||
use syn::punctuated::{Pair, Punctuated};
|
||||
use syn::punctuated::Punctuated;
|
||||
|
||||
|
||||
type Config = Punctuated<Config, Token![,]>;
|
||||
type Config = Punctuated<ConfigItem, Token![,]>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ConfigItem {
|
||||
identifier: Ident,
|
||||
types: ConfigTypes,
|
||||
types: Vec<ConfigPair>,
|
||||
}
|
||||
|
||||
type ConfigTypes = Punctuated<Type, Conversion>;
|
||||
#[derive(Clone)]
|
||||
struct ConfigPair {
|
||||
conversion: Conversion,
|
||||
r#type: Type,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
enum Conversion {
|
||||
From,
|
||||
TryFrom,
|
||||
|
@ -25,11 +31,28 @@ enum Conversion {
|
|||
impl Parse for ConfigItem {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let identifier = input.parse::<Ident>()?;
|
||||
let types = input.parse::<Punctuated<Type, Conversion>>()?;
|
||||
|
||||
input.parse::<Token![:]>()?;
|
||||
input.parse::<Type>()?;
|
||||
|
||||
let mut types = vec![];
|
||||
while let Ok(typ) = input.parse::<ConfigPair>() {
|
||||
types.push(typ)
|
||||
}
|
||||
|
||||
Ok(Self { identifier, types })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ConfigPair {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let conversion = input.parse::<Conversion>()?;
|
||||
let r#type = input.parse::<Type>()?;
|
||||
|
||||
Ok(Self { conversion, r#type })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Conversion {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
if input.parse::<Token![->]>().is_ok() {
|
||||
|
@ -49,67 +72,66 @@ impl Parse for Conversion {
|
|||
|
||||
#[proc_macro]
|
||||
pub fn config(input: TokenStream) -> TokenStream {
|
||||
let input: Config = parse_macro_input!(input as Config);
|
||||
let input: Config = parse_macro_input!(input with syn::punctuated::Punctuated::parse_terminated);
|
||||
|
||||
let cache = quote! {
|
||||
let cache_code = quote! {
|
||||
mod _cache {
|
||||
pub static lock: std::sync::OnceLock<Cache> = std::sync::OnceLock::new();
|
||||
pub static lock: std::sync::OnceLock<micronfig::cache::Cache> = std::sync::OnceLock::new();
|
||||
}
|
||||
|
||||
pub(self) fn _cache() {
|
||||
fn _cache() -> &'static micronfig::cache::Cache {
|
||||
_cache::lock.get_or_init(micronfig::cache::Cache::new)
|
||||
}
|
||||
};
|
||||
|
||||
let items = input.iter().map(|item: ConfigItem| {
|
||||
let identifier = item.identifier;
|
||||
let items = input.iter().map(|item: &ConfigItem| {
|
||||
let identifier = &item.identifier;
|
||||
let identifier_string = identifier.to_string();
|
||||
|
||||
// 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() {
|
||||
let mut current_type: &Type = match pair {
|
||||
Pair::Punctuated(ty, _) => ty,
|
||||
Pair::End(ty) => ty,
|
||||
};
|
||||
let next_conversion: Option<&Conversion> = match pair {
|
||||
Pair::Punctuated(_, cv) => Some(cv),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(previous_conversion) = previous_conversion {
|
||||
conversion_code = match previous_conversion {
|
||||
for ConfigPair { r#type, conversion } in item.types.iter() {
|
||||
let typ = r#type;
|
||||
conversion_code = match conversion {
|
||||
Conversion::From => quote! {
|
||||
#conversion_code
|
||||
let value: #current_type = value.into();
|
||||
}
|
||||
let value: Option<#typ> = value.map(Into::into);
|
||||
},
|
||||
Conversion::TryFrom => quote! {
|
||||
#conversion_code
|
||||
let value: #current_type = value.try_into()
|
||||
.expect("to be able to convert {}", stringify!(#identifier));
|
||||
}
|
||||
let value: Option<#typ> = value
|
||||
.map(|v| v.try_into())
|
||||
.map(|v| v.expect(&format!("to be able to convert {}", #identifier_string))
|
||||
);
|
||||
},
|
||||
Conversion::FromStr => quote! {
|
||||
#conversion_code
|
||||
let value: #current_type = value.parse()
|
||||
.expect("to be able to parse {}", stringify!(#identifier));
|
||||
}
|
||||
let value: Option<#typ> = value
|
||||
.map(|v| v.parse())
|
||||
.map(|v| v.expect(&format!("to be able to parse {}", #identifier_string))
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
previous_conversion = next_conversion;
|
||||
};
|
||||
|
||||
let last_type = item.types.last();
|
||||
let last_type = match item.types.last() {
|
||||
Some(pair) => {
|
||||
let typ = pair.r#type.clone();
|
||||
quote! { #typ }
|
||||
},
|
||||
None => {
|
||||
quote! { String }
|
||||
},
|
||||
};
|
||||
|
||||
quote! {
|
||||
mod #identifier {
|
||||
pub(super) lock: std::sync::OnceLock<#last_type> = std::sync::OnceLock::new();
|
||||
pub(super) static lock: std::sync::OnceLock<Option<#last_type>> = std::sync::OnceLock::new();
|
||||
}
|
||||
|
||||
pub(crate) fn #identifier() {
|
||||
pub(crate) fn #identifier() -> &'static Option<#last_type> {
|
||||
#identifier::lock.get_or_init(|| {
|
||||
let key = stringify!(#identifier);
|
||||
let value = _cache().get(&key);
|
||||
let key: std::ffi::OsString = #identifier_string.into();
|
||||
let value: Option<String> = _cache().get(&key);
|
||||
|
||||
#conversion_code
|
||||
|
||||
|
@ -119,8 +141,20 @@ pub fn config(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#cache
|
||||
#items
|
||||
let mut items_code = quote! {};
|
||||
for code in items {
|
||||
items_code = quote! {
|
||||
#items_code
|
||||
#code
|
||||
};
|
||||
}
|
||||
|
||||
let quote = quote! {
|
||||
#cache_code
|
||||
#items_code
|
||||
};
|
||||
|
||||
println!("{quote}");
|
||||
|
||||
quote.into()
|
||||
}
|
41
micronfig_macros/tests/config.rs
Normal file
41
micronfig_macros/tests/config.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use micronfig_macros::config;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
config! {
|
||||
GARAS: String,
|
||||
AUTO: String,
|
||||
BUS: String,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
config! {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_simple() {
|
||||
config! {
|
||||
GARAS: String > u32,
|
||||
AUTO: String > u16,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn implicit() {
|
||||
config! {
|
||||
GARAS,
|
||||
AUTO,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_implicit() {
|
||||
config! {
|
||||
GARAS: > u32,
|
||||
AUTO > u16,
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,7 +0,0 @@
|
|||
use micronfig;
|
||||
|
||||
config! {
|
||||
GARAS: String,
|
||||
AUTO: String,
|
||||
BUS: String,
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
use micronfig;
|
||||
|
||||
config! {}
|
|
@ -1,5 +0,0 @@
|
|||
#[test]
|
||||
fn trybuild() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.pass("tests/configs/*.rs");
|
||||
}
|
Loading…
Reference in a new issue