mirror of
https://github.com/OMGeeky/confique.git
synced 2026-01-02 01:26:28 +01:00
Add support for array default values
This commit is contained in:
109
macro/src/gen.rs
109
macro/src/gen.rs
@@ -1,5 +1,5 @@
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{Ident, spanned::Spanned};
|
||||
|
||||
use crate::ir::{self, Expr, FieldKind, LeafKind};
|
||||
@@ -135,13 +135,14 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
|
||||
input.name,
|
||||
f.name,
|
||||
);
|
||||
let expr = default_deserialize_expr(&default);
|
||||
|
||||
match deserialize_with {
|
||||
None => quote! {
|
||||
Some(confique::internal::deserialize_default(#default).expect(#msg))
|
||||
Some(confique::internal::deserialize_default(#expr).expect(#msg))
|
||||
},
|
||||
Some(p) => quote! {
|
||||
Some(#p(confique::internal::into_deserializer(#default)).expect(#msg))
|
||||
Some(#p(confique::internal::into_deserializer(#expr)).expect(#msg))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -308,7 +309,13 @@ fn gen_meta(input: &ir::Input) -> TokenStream {
|
||||
}
|
||||
FieldKind::Leaf { env, kind: LeafKind::Required { default, ty, .. }, ..} => {
|
||||
let env = env_tokens(env);
|
||||
let default_value = gen_meta_default(default, &ty);
|
||||
let default_value = match default {
|
||||
Some(default) => {
|
||||
let meta = gen_meta_default(default, Some(&ty));
|
||||
quote! { Some(#meta) }
|
||||
},
|
||||
None => quote! { None },
|
||||
};
|
||||
quote! {
|
||||
confique::meta::FieldKind::Leaf {
|
||||
env: #env,
|
||||
@@ -340,7 +347,7 @@ fn gen_meta(input: &ir::Input) -> TokenStream {
|
||||
|
||||
/// 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 gen_meta_default(default: &Expr, ty: Option<&syn::Type>) -> TokenStream {
|
||||
fn int_type_to_variant(suffix: &str) -> Option<&'static str> {
|
||||
match suffix {
|
||||
"u8" => Some("U8"),
|
||||
@@ -373,13 +380,13 @@ fn gen_meta_default(default: &Option<Expr>, ty: &syn::Type) -> TokenStream {
|
||||
// that. Otherwise we use a default.
|
||||
fn infer_type(
|
||||
suffix: &str,
|
||||
field_ty: &syn::Type,
|
||||
field_ty: Option<&syn::Type>,
|
||||
default: &str,
|
||||
map: fn(&str) -> Option<&'static str>,
|
||||
) -> Ident {
|
||||
let variant = map(suffix)
|
||||
.or_else(|| {
|
||||
if let syn::Type::Path(syn::TypePath { qself: None, path }) = field_ty {
|
||||
if let Some(syn::Type::Path(syn::TypePath { qself: None, path })) = field_ty {
|
||||
path.get_ident().and_then(|i| map(&i.to_string()))
|
||||
} else {
|
||||
None
|
||||
@@ -390,35 +397,75 @@ fn gen_meta_default(default: &Option<Expr>, ty: &syn::Type) -> TokenStream {
|
||||
Ident::new(variant, Span::call_site())
|
||||
}
|
||||
|
||||
/// Tries to extract the type of the item of a field with an array default value.
|
||||
fn get_item_ty(ty: &syn::Type) -> Option<&syn::Type> {
|
||||
match ty {
|
||||
syn::Type::Slice(slice) => Some(&slice.elem),
|
||||
syn::Type::Array(array) => Some(&*array.elem),
|
||||
syn::Type::Path(p) => {
|
||||
// This is the least clear case. We certainly want to cover
|
||||
// `Vec<T>` but ideally some more cases. On the other hand, we
|
||||
// just can't really know, so some incorrect guesses are
|
||||
// definitely expected here. Most are likely filtered out by
|
||||
// applying `gen_meta_default` to it, but some will result in a
|
||||
// wrong default value type. But people can always just add a
|
||||
// prefix to the literal in those cases.
|
||||
//
|
||||
// We simply check if the last element in the path has exactly
|
||||
// one generic type argument, in which case we use that.
|
||||
let args = match &p.path.segments.last().expect("empty type path").arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => &args.args,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
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)) }
|
||||
}
|
||||
};
|
||||
if args.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
quote! { Some(#v) }
|
||||
} else {
|
||||
quote! { None }
|
||||
match &args[0] {
|
||||
syn::GenericArgument::Type(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
syn::Type::Reference(r) => get_item_ty(&r.elem),
|
||||
syn::Type::Group(g) => get_item_ty(&g.elem),
|
||||
syn::Type::Paren(p) => get_item_ty(&p.elem),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)) }
|
||||
}
|
||||
Expr::Array(items) => {
|
||||
let item_type = ty.and_then(get_item_ty);
|
||||
let items = items.iter().map(|item| gen_meta_default(item, item_type));
|
||||
quote! { confique::meta::Expr::Array(&[#( #items ),*]) }
|
||||
}
|
||||
};
|
||||
|
||||
quote! { #v }
|
||||
}
|
||||
|
||||
impl ToTokens for ir::Expr {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Self::Str(lit) => lit.to_tokens(tokens),
|
||||
Self::Int(lit) => lit.to_tokens(tokens),
|
||||
Self::Float(lit) => lit.to_tokens(tokens),
|
||||
Self::Bool(lit) => lit.to_tokens(tokens),
|
||||
}
|
||||
fn default_deserialize_expr(expr: &ir::Expr) -> TokenStream {
|
||||
match expr {
|
||||
ir::Expr::Str(lit) => quote! { #lit },
|
||||
ir::Expr::Int(lit) => quote! { #lit },
|
||||
ir::Expr::Float(lit) => quote! { #lit },
|
||||
ir::Expr::Bool(lit) => quote! { #lit },
|
||||
ir::Expr::Array(arr) => {
|
||||
let items = arr.iter().map(default_deserialize_expr);
|
||||
quote! { confique::internal::ArrayIntoDeserializer([ #(#items),* ]) }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Definition of the intermediate representation.
|
||||
|
||||
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned};
|
||||
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated};
|
||||
|
||||
use crate::util::{is_option, unwrap_option};
|
||||
|
||||
@@ -70,7 +70,7 @@ pub(crate) enum Expr {
|
||||
Int(syn::LitInt),
|
||||
Float(syn::LitFloat),
|
||||
Bool(syn::LitBool),
|
||||
// TODO: arrays?
|
||||
Array(Vec<Expr>),
|
||||
}
|
||||
|
||||
impl Input {
|
||||
@@ -173,17 +173,26 @@ impl Field {
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
fn from_lit(lit: syn::Lit) -> Result<Self, Error> {
|
||||
match lit {
|
||||
syn::Lit::Str(l) => Ok(Self::Str(l)),
|
||||
syn::Lit::Int(l) => Ok(Self::Int(l)),
|
||||
syn::Lit::Float(l) => Ok(Self::Float(l)),
|
||||
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
|
||||
impl Parse for Expr {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
let msg = "invalid default value. Allowed are only: certain literals \
|
||||
(string, integer, float, bool), and arrays";
|
||||
|
||||
_ => {
|
||||
let msg = "only string, integer, float and bool literals are allowed here";
|
||||
Err(Error::new(lit.span(), msg))
|
||||
if input.peek(syn::token::Bracket) {
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
|
||||
let items = <Punctuated<Expr, Token![,]>>::parse_terminated(&content)?;
|
||||
Ok(Self::Array(items.into_iter().collect()))
|
||||
} else {
|
||||
let lit: syn::Lit = input.parse()
|
||||
.map_err(|_| Error::new(input.span(), msg))?;
|
||||
match lit {
|
||||
syn::Lit::Str(l) => Ok(Self::Str(l)),
|
||||
syn::Lit::Int(l) => Ok(Self::Int(l)),
|
||||
syn::Lit::Float(l) => Ok(Self::Float(l)),
|
||||
syn::Lit::Bool(l) => Ok(Self::Bool(l)),
|
||||
_ => Err(Error::new(lit.span(), msg)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +248,7 @@ fn extract_internal_attrs(
|
||||
|
||||
let mut out = InternalAttrs::default();
|
||||
for attr in internal_attrs {
|
||||
type AttrList = syn::punctuated::Punctuated<InternalAttr, Token![,]>;
|
||||
type AttrList = Punctuated<InternalAttr, Token![,]>;
|
||||
let parsed_list = attr.parse_args_with(AttrList::parse_terminated)?;
|
||||
|
||||
for parsed in parsed_list {
|
||||
@@ -315,7 +324,7 @@ impl Parse for InternalAttr {
|
||||
|
||||
"default" => {
|
||||
let _: Token![=] = input.parse()?;
|
||||
let expr = Expr::from_lit(input.parse()?)?;
|
||||
let expr: Expr = input.parse()?;
|
||||
assert_empty_or_comma(input)?;
|
||||
Ok(Self::Default(expr))
|
||||
}
|
||||
|
||||
@@ -62,3 +62,19 @@ pub fn from_env_with<T>(
|
||||
}.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// `serde` does not implement `IntoDeserializer` for fixed size arrays. This
|
||||
/// helper type is just used for this purpose.
|
||||
pub struct ArrayIntoDeserializer<T, const N: usize>(pub [T; N]);
|
||||
|
||||
impl<'de, T, E, const N: usize> serde::de::IntoDeserializer<'de, E> for ArrayIntoDeserializer<T, N>
|
||||
where
|
||||
T: serde::de::IntoDeserializer<'de, E>,
|
||||
E: serde::de::Error,
|
||||
{
|
||||
type Deserializer = serde::de::value::SeqDeserializer<std::array::IntoIter<T, N>, E>;
|
||||
|
||||
fn into_deserializer(self) -> Self::Deserializer {
|
||||
serde::de::value::SeqDeserializer::new(self.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ pub enum Expr {
|
||||
Float(Float),
|
||||
Integer(Integer),
|
||||
Bool(bool),
|
||||
Array(&'static [Expr]),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
||||
12
src/toml.rs
12
src/toml.rs
@@ -193,6 +193,18 @@ impl fmt::Display for PrintExpr {
|
||||
Expr::Float(v) => v.fmt(f),
|
||||
Expr::Integer(v) => v.fmt(f),
|
||||
Expr::Bool(v) => v.fmt(f),
|
||||
Expr::Array(items) => {
|
||||
// TODO: pretty printing of long arrays onto multiple lines?
|
||||
f.write_char('[')?;
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
PrintExpr(item).fmt(f)?;
|
||||
}
|
||||
f.write_char(']')?;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/yaml.rs
12
src/yaml.rs
@@ -184,6 +184,18 @@ impl fmt::Display for PrintExpr {
|
||||
Expr::Float(v) => v.fmt(f),
|
||||
Expr::Integer(v) => v.fmt(f),
|
||||
Expr::Bool(v) => v.fmt(f),
|
||||
Expr::Array(items) => {
|
||||
// TODO: pretty printing of long arrays onto multiple lines?
|
||||
f.write_char('[')?;
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
PrintExpr(item).fmt(f)?;
|
||||
}
|
||||
f.write_char(']')?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user