1
Fork 0
mirror of https://github.com/Steffo99/micronfig.git synced 2024-11-25 01:24:18 +00:00

It workssssssssss

This commit is contained in:
Steffo 2024-01-02 11:53:46 +01:00
parent e2a1726626
commit b98d2da4e9
Signed by: steffo
GPG key ID: 2A24051445686895
11 changed files with 146 additions and 89 deletions

View file

@ -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" />

View file

@ -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)
); );
} }
} }

View file

@ -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())
} }

View file

@ -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);

View file

@ -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()
} }

View file

@ -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]

View file

@ -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,
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 {
Conversion::From => quote! { Conversion::From => quote! {
#conversion_code #conversion_code
let value: #current_type = value.into(); let value: Option<#typ> = value.map(Into::into);
} },
Conversion::TryFrom => quote! { Conversion::TryFrom => quote! {
#conversion_code #conversion_code
let value: #current_type = value.try_into() let value: Option<#typ> = value
.expect("to be able to convert {}", stringify!(#identifier)); .map(|v| v.try_into())
} .map(|v| v.expect(&format!("to be able to convert {}", #identifier_string))
);
},
Conversion::FromStr => quote! { Conversion::FromStr => quote! {
#conversion_code #conversion_code
let value: #current_type = value.parse() let value: Option<#typ> = value
.expect("to be able to parse {}", stringify!(#identifier)); .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! { 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()
} }

View 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,
}
}
*/

View file

@ -1,7 +0,0 @@
use micronfig;
config! {
GARAS: String,
AUTO: String,
BUS: String,
}

View file

@ -1,3 +0,0 @@
use micronfig;
config! {}

View file

@ -1,5 +0,0 @@
#[test]
fn trybuild() {
let t = trybuild::TestCases::new();
t.pass("tests/configs/*.rs");
}