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">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<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="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">
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
<option name="processCode" value="true" />
|
<option name="processCode" value="true" />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Definition of [`Cache`].
|
//! Definition of [`Cache`].
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
/// Cache initialized only once per config block and used to quickly retrieve configuration values.
|
/// 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.
|
/// Get a value from the cache.
|
||||||
pub fn get<Key>(&self, key: Key) -> Option<String>
|
pub fn get(&self, key: &OsStr) -> Option<String>
|
||||||
where Key: AsRef<&std::ffi::OsStr>
|
|
||||||
{
|
{
|
||||||
let mut value = None;
|
let mut value = None;
|
||||||
|
|
||||||
if cfg!(feature = "envfiles") {
|
if cfg!(feature = "envfiles") {
|
||||||
value = crate::envfiles::get(format!("{key}_FILE"));
|
value = crate::envfiles::get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg!(feature = "envvars") && value.is_none() {
|
if cfg!(feature = "envvars") && value.is_none() {
|
||||||
value = crate::envvars::get(&key);
|
value = crate::envvars::get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg!(feature = "envdot") && value.is_none() {
|
if cfg!(feature = "envdot") && value.is_none() {
|
||||||
for dotenv in self.envdot.iter() {
|
for dotenv in self.envdot.iter() {
|
||||||
value = crate::envdot::get(dotenv, &key);
|
value = crate::envdot::get(dotenv, key);
|
||||||
if value.is_some() {
|
if value.is_some() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ impl Cache {
|
||||||
where Path: AsRef<std::path::Path> + Debug
|
where Path: AsRef<std::path::Path> + Debug
|
||||||
{
|
{
|
||||||
self.envdot.push(
|
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.
|
//! Utilities for fetching configuration values defined in specific `.env` files.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
@ -12,11 +12,7 @@ use regex::Regex;
|
||||||
pub type DotEnv = HashMap<OsString, String>;
|
pub type DotEnv = HashMap<OsString, String>;
|
||||||
|
|
||||||
/// Parse a `.env` file.
|
/// Parse a `.env` file.
|
||||||
///
|
pub fn parse_dotenv<P>(value: P) -> DotEnv
|
||||||
/// ### Warning
|
|
||||||
///
|
|
||||||
/// This method isn't properly
|
|
||||||
fn parse_dotenv<P>(value: P) -> DotEnv
|
|
||||||
where P: AsRef<Path> + Debug
|
where P: AsRef<Path> + Debug
|
||||||
{
|
{
|
||||||
let mut file = File::open(&value)
|
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.
|
/// Get the requested variable from a [`DotEnv`] structure.
|
||||||
pub fn get<Key>(dotenv: &DotEnv, key: Key) -> Option<String>
|
pub fn get(dotenv: &DotEnv, key: &OsStr) -> Option<String>
|
||||||
where Key: AsRef<&std::ffi::OsStr>
|
|
||||||
{
|
{
|
||||||
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.
|
//! Contents of files at paths defined by environment variables.
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
/// Get the contents of the file at the path specified by the requested environment variable plus `_FILE`.
|
/// 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>
|
pub fn get(key: &OsStr) -> Option<String> {
|
||||||
where Key: AsRef<std::ffi::OsStr>,
|
let mut key: std::ffi::OsString = key.to_os_string();
|
||||||
{
|
key.push("_FILE");
|
||||||
let path = std::env::var(format!("{key}_FILE")).ok()?;
|
let path = std::env::var(key).ok()?;
|
||||||
|
|
||||||
let path = std::ffi::OsString::from(path);
|
let path = std::ffi::OsString::from(path);
|
||||||
let path = std::path::PathBuf::from(path);
|
let path = std::path::PathBuf::from(path);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//! Environment variables.
|
//! Environment variables.
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
/// Get the specified environment variable.
|
/// Get the specified environment variable.
|
||||||
pub fn get<Key>(key: Key) -> Option<String>
|
pub fn get(key: &OsStr) -> Option<String> {
|
||||||
where Key: AsRef<std::ffi::OsStr>,
|
|
||||||
{
|
|
||||||
std::env::var(key).ok()
|
std::env::var(key).ok()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ syn = "2.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
trybuild = "1.0"
|
|
||||||
micronfig = { version = "0.3.0", path = "../micronfig" }
|
micronfig = { version = "0.3.0", path = "../micronfig" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::{quote, quote_spanned};
|
use quote::quote;
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
use syn::{Ident, parse_macro_input, Token, Type};
|
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 {
|
struct ConfigItem {
|
||||||
identifier: Ident,
|
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 {
|
enum Conversion {
|
||||||
From,
|
From,
|
||||||
TryFrom,
|
TryFrom,
|
||||||
|
@ -25,11 +31,28 @@ enum Conversion {
|
||||||
impl Parse for ConfigItem {
|
impl Parse for ConfigItem {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
let identifier = input.parse::<Ident>()?;
|
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 })
|
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 {
|
impl Parse for Conversion {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
if input.parse::<Token![->]>().is_ok() {
|
if input.parse::<Token![->]>().is_ok() {
|
||||||
|
@ -49,67 +72,66 @@ impl Parse for Conversion {
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn config(input: TokenStream) -> TokenStream {
|
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 {
|
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)
|
_cache::lock.get_or_init(micronfig::cache::Cache::new)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let items = input.iter().map(|item: ConfigItem| {
|
let items = input.iter().map(|item: &ConfigItem| {
|
||||||
let identifier = item.identifier;
|
let identifier = &item.identifier;
|
||||||
|
let identifier_string = identifier.to_string();
|
||||||
|
|
||||||
// TODO: Can types be zero-length?
|
|
||||||
let mut conversion_code = quote! {};
|
let mut conversion_code = quote! {};
|
||||||
let mut previous_conversion: Option<&Conversion> = None;
|
for ConfigPair { r#type, conversion } in item.types.iter() {
|
||||||
for pair in item.types.pairs().into_iter() {
|
let typ = r#type;
|
||||||
let mut current_type: &Type = match pair {
|
conversion_code = match conversion {
|
||||||
Pair::Punctuated(ty, _) => ty,
|
Conversion::From => quote! {
|
||||||
Pair::End(ty) => ty,
|
#conversion_code
|
||||||
|
let value: Option<#typ> = value.map(Into::into);
|
||||||
|
},
|
||||||
|
Conversion::TryFrom => quote! {
|
||||||
|
#conversion_code
|
||||||
|
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: Option<#typ> = value
|
||||||
|
.map(|v| v.parse())
|
||||||
|
.map(|v| v.expect(&format!("to be able to parse {}", #identifier_string))
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
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 {
|
|
||||||
Conversion::From => quote! {
|
|
||||||
#conversion_code
|
|
||||||
let value: #current_type = value.into();
|
|
||||||
}
|
|
||||||
Conversion::TryFrom => quote! {
|
|
||||||
#conversion_code
|
|
||||||
let value: #current_type = value.try_into()
|
|
||||||
.expect("to be able to convert {}", stringify!(#identifier));
|
|
||||||
}
|
|
||||||
Conversion::FromStr => quote! {
|
|
||||||
#conversion_code
|
|
||||||
let value: #current_type = value.parse()
|
|
||||||
.expect("to be able to parse {}", stringify!(#identifier));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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! {
|
quote! {
|
||||||
mod #identifier {
|
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(|| {
|
#identifier::lock.get_or_init(|| {
|
||||||
let key = stringify!(#identifier);
|
let key: std::ffi::OsString = #identifier_string.into();
|
||||||
let value = _cache().get(&key);
|
let value: Option<String> = _cache().get(&key);
|
||||||
|
|
||||||
#conversion_code
|
#conversion_code
|
||||||
|
|
||||||
|
@ -119,8 +141,20 @@ pub fn config(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
quote! {
|
let mut items_code = quote! {};
|
||||||
#cache
|
for code in items {
|
||||||
#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