Add support for #[derive] annotations

This commit is contained in:
Lukas Kalbertodt
2021-04-29 20:15:30 +02:00
parent 32f05abdca
commit 5d3ccd7b42
5 changed files with 37 additions and 4 deletions

View File

@@ -3,6 +3,7 @@ mod config {
use std::path::PathBuf;
confique::config! {
#[derive(Clone)]
log: {
/// Determines how many messages are logged. Log messages below
/// this level are not emitted. Possible values: "trace", "debug",
@@ -12,8 +13,8 @@ mod config {
/// If this is set, log messages are also written to this file.
#[example = "/var/log/test.log"]
file: Option<PathBuf>,
}
},
},
}
}

View File

@@ -15,6 +15,8 @@ pub(crate) struct Input {
pub(crate) enum Node {
Internal {
doc: Vec<String>,
/// Attributes that are used as specified and not interpreted by us.
attrs: Vec<syn::Attribute>,
name: syn::Ident,
children: Vec<Node>,
},

View File

@@ -126,7 +126,7 @@ fn gen_raw_mod(input: &Input, visibility: &TokenStream) -> TokenStream {
fn gen_root_mod(input: &Input, visibility: &TokenStream) -> TokenStream {
let mut out = TokenStream::new();
visit(input, |node, path| {
if let Node::Internal { name, doc, children } = node {
if let Node::Internal { name, doc, attrs, children } = node {
let type_name = to_camel_case(name);
let user_fields = collect_tokens(children, |node| {
@@ -168,9 +168,18 @@ fn gen_root_mod(input: &Input, visibility: &TokenStream) -> TokenStream {
}
});
// We add some derives if the user has not specified any
let derive = if attrs.iter().any(|attr| attr.path.is_ident("derive")) {
quote! {}
} else {
quote! { #[derive(Debug)] }
};
out.extend(quote! {
#( #[doc = #doc] )*
#[derive(Debug)]
#( #attrs )*
#derive
#visibility struct #type_name {
#user_fields
}

View File

@@ -15,6 +15,13 @@ impl Parse for Input {
let mut outer_attrs = input.call(syn::Attribute::parse_inner)?;
let doc = extract_doc(&mut outer_attrs)?;
// Extract attributes that we will just forward/emit verbatim. Well, not
// completely verbatim: we have to change the type to outer attribute.
let mut forwarded_attrs = extract_attrs(&["derive"], &mut outer_attrs);
for a in &mut forwarded_attrs {
a.style = syn::AttrStyle::Outer;
}
// `#![visibility = "..."]`
let visibility = extract_single_name_value_attr("visibility", &mut outer_attrs)?
.map(|v| Ok::<_, syn::Error>(assert_string_lit(v)?.parse::<TokenStream>()?))
@@ -25,6 +32,7 @@ impl Parse for Input {
let root = Node::Internal {
doc,
attrs: forwarded_attrs,
name: Ident::new("config", Span::call_site()),
children: children.into_iter().collect(),
};
@@ -45,6 +53,7 @@ impl Parse for Node {
let out = if input.lookahead1().peek(syn::token::Brace) {
// --- A nested Internal ---
let forwarded_attrs = extract_attrs(&["derive"], &mut attrs);
let inner;
syn::braced!(inner in input);
@@ -52,6 +61,7 @@ impl Parse for Node {
Self::Internal {
doc,
attrs: forwarded_attrs,
name,
children: fields.into_iter().collect(),
}
@@ -148,6 +158,15 @@ fn extract_doc(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<String>, Error> {
Ok(out)
}
/// Extracts all attributes with a path contained in `names`.
fn extract_attrs(names: &[&str], attrs: &mut Vec<syn::Attribute>) -> Vec<syn::Attribute> {
let (matches, rest) = attrs.drain(..)
.partition(|attr| names.iter().any(|n| attr.path.is_ident(n)));
*attrs = rest;
matches
}
fn extract_single_name_value_attr(
name: &str,
attrs: &mut Vec<syn::Attribute>,

View File

@@ -14,7 +14,9 @@ use crate as confique;
crate::config! {
//! An example configuration.
#![visibility = "pub"]
#![derive(Clone)]
#[derive(Clone, Copy)]
dns: {
/// The DNS server IP address.
#[example = "1.1.1.1"]