diff --git a/examples/simple.rs b/examples/simple.rs index 15a1eba..a3d8289 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -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"), diff --git a/macro/src/gen.rs b/macro/src/gen.rs index 7e710ca..6c5ce48 100644 --- a/macro/src/gen.rs +++ b/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, 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 diff --git a/src/lib.rs b/src/lib.rs index 83522fd..04db86b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/meta.rs b/src/meta.rs new file mode 100644 index 0000000..b8d1178 --- /dev/null +++ b/src/meta.rs @@ -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, + }, + 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), +}