mirror of
https://github.com/OMGeeky/confique.git
synced 2026-02-14 23:24:46 +01:00
Add Config::META to access the configuration definition at runtime
This commit is contained in:
@@ -27,6 +27,8 @@ struct Cat {
|
||||
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
println!("{:#?}", Conf::META);
|
||||
|
||||
let r = Conf::from_sources(&[
|
||||
&Path::new("examples/files/simple.toml"),
|
||||
&Path::new("examples/files/etc/simple.yaml"),
|
||||
|
||||
121
macro/src/gen.rs
121
macro/src/gen.rs
@@ -1,8 +1,8 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{ToTokens, format_ident, quote};
|
||||
use syn::Ident;
|
||||
|
||||
use crate::ir::{self, FieldKind};
|
||||
use crate::ir::{self, Expr, FieldKind};
|
||||
|
||||
|
||||
pub(crate) fn gen(input: ir::Input) -> TokenStream {
|
||||
@@ -38,6 +38,8 @@ fn gen_config_impl(input: &ir::Input) -> TokenStream {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let meta_item = gen_meta(input);
|
||||
quote! {
|
||||
impl confique::Config for #name {
|
||||
type Partial = #partial_mod_name::#partial_struct_name;
|
||||
@@ -47,6 +49,8 @@ fn gen_config_impl(input: &ir::Input) -> TokenStream {
|
||||
#( #field_names: #from_exprs, )*
|
||||
})
|
||||
}
|
||||
|
||||
#meta_item
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,6 +158,119 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
|
||||
}
|
||||
|
||||
|
||||
/// Generates the whole `const META` item.
|
||||
fn gen_meta(input: &ir::Input) -> TokenStream {
|
||||
let name_str = input.name.to_string();
|
||||
let doc = &input.doc;
|
||||
let meta_fields = input.fields.iter().map(|f| {
|
||||
let name = f.name.to_string();
|
||||
let doc = &f.doc;
|
||||
let kind = match &f.kind {
|
||||
FieldKind::Child => {
|
||||
let ty = &f.ty;
|
||||
quote! {
|
||||
confique::meta::FieldKind::Child { meta: &<#ty as confique::Config>::META }
|
||||
}
|
||||
}
|
||||
FieldKind::Leaf { default } => {
|
||||
let default_value = gen_meta_default(default, &f.ty);
|
||||
quote! {
|
||||
confique::meta::FieldKind::Leaf { default: #default_value }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
confique::meta::Field {
|
||||
name: #name,
|
||||
doc: &[ #(#doc),* ],
|
||||
kind: #kind,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
const META: confique::meta::Meta = confique::meta::Meta {
|
||||
name: #name_str,
|
||||
doc: &[ #(#doc),* ],
|
||||
fields: &[ #( #meta_fields ),* ],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the meta expression of type `meta::Expr` to be used for the
|
||||
/// `default` field.
|
||||
fn gen_meta_default(default: &Option<Expr>, ty: &syn::Type) -> TokenStream {
|
||||
fn int_type_to_variant(suffix: &str) -> Option<&'static str> {
|
||||
match suffix {
|
||||
"u8" => Some("U8"),
|
||||
"u16" => Some("U16"),
|
||||
"u32" => Some("U32"),
|
||||
"u64" => Some("U64"),
|
||||
"u128" => Some("U128"),
|
||||
"usize" => Some("Usize"),
|
||||
"i8" => Some("I8"),
|
||||
"i16" => Some("I16"),
|
||||
"i32" => Some("I32"),
|
||||
"i64" => Some("I64"),
|
||||
"i128" => Some("I128"),
|
||||
"isize" => Some("Isize"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn float_type_to_variant(suffix: &str) -> Option<&'static str> {
|
||||
match suffix {
|
||||
"f32" => Some("F32"),
|
||||
"f64" => Some("F64"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// To figure out the type of int or float literals, we first look at the
|
||||
// type suffix of the literal. If it is specified, we use that. Otherwise
|
||||
// we check if the field type is a known float/integer type. If so, we use
|
||||
// that. Otherwise we use a default.
|
||||
fn infer_type(
|
||||
suffix: &str,
|
||||
field_ty: &syn::Type,
|
||||
default: &str,
|
||||
map: fn(&str) -> Option<&'static str>,
|
||||
) -> Ident {
|
||||
let variant = int_type_to_variant(suffix)
|
||||
.or_else(|| {
|
||||
if let syn::Type::Path(syn::TypePath { qself: None, path }) = field_ty {
|
||||
path.get_ident().and_then(|i| map(&i.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(default);
|
||||
|
||||
Ident::new(variant, Span::call_site())
|
||||
}
|
||||
|
||||
|
||||
if let Some(default) = default {
|
||||
let v = match default {
|
||||
Expr::Bool(v) => quote! { confique::meta::Expr::Bool(#v) },
|
||||
Expr::Str(s) => quote! { confique::meta::Expr::Str(#s) },
|
||||
Expr::Int(i) => {
|
||||
let variant = infer_type(i.suffix(), ty, "I32", int_type_to_variant);
|
||||
quote! { confique::meta::Expr::Integer(confique::meta::Integer::#variant(#i)) }
|
||||
}
|
||||
Expr::Float(f) => {
|
||||
let variant = infer_type(f.suffix(), ty, "F64", float_type_to_variant);
|
||||
quote! { confique::meta::Expr::Float(confique::meta::Float::#variant(#f)) }
|
||||
}
|
||||
};
|
||||
|
||||
quote! { Some(#v) }
|
||||
} else {
|
||||
quote! { None }
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given type is an `Option` and if so, return the inner type.
|
||||
///
|
||||
/// Note: this function clearly shows one of the major shortcomings of proc
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod internal;
|
||||
|
||||
mod error;
|
||||
mod file;
|
||||
pub mod meta;
|
||||
|
||||
|
||||
pub use serde;
|
||||
@@ -40,6 +41,12 @@ pub trait Config: Sized {
|
||||
/// values defined.
|
||||
type Partial: Partial;
|
||||
|
||||
/// A description of this configuration.
|
||||
///
|
||||
/// This is a runtime representation from the struct definition of your
|
||||
/// configuration type.
|
||||
const META: meta::Meta;
|
||||
|
||||
/// Tries to create `Self` from a potentially partial object.
|
||||
///
|
||||
/// If any required values are not defined in `partial`, an [`Error`] is
|
||||
|
||||
64
src/meta.rs
Normal file
64
src/meta.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
//! Types for [`Config::META`][super::Config::META]. Represent information about
|
||||
//! a configuration type.
|
||||
|
||||
// TODO: having all these fields public make me uncomfortable. For now it's
|
||||
// fine, but before reaching 1.0 I need to figure out how to allow future
|
||||
// additions without breaking stuff.
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Meta {
|
||||
/// The type (struct) name.
|
||||
pub name: &'static str,
|
||||
|
||||
/// Doc comments.
|
||||
pub doc: &'static [&'static str],
|
||||
|
||||
pub fields: &'static [Field],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Field {
|
||||
pub name: &'static str,
|
||||
pub kind: FieldKind,
|
||||
pub doc: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum FieldKind {
|
||||
Leaf {
|
||||
default: Option<Expr>,
|
||||
},
|
||||
Child {
|
||||
meta: &'static Meta,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Expr {
|
||||
Str(&'static str),
|
||||
Float(Float),
|
||||
Integer(Integer),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Float {
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Integer {
|
||||
U8(u8),
|
||||
U16(u16),
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
U128(u128),
|
||||
Usize(usize),
|
||||
I8(i8),
|
||||
I16(i16),
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
I128(i128),
|
||||
Isize(isize),
|
||||
}
|
||||
Reference in New Issue
Block a user