From 5d3ccd7b422454d951a22d644fe6ef03cb30f8f8 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 29 Apr 2021 20:15:30 +0200 Subject: [PATCH] Add support for `#[derive]` annotations --- examples/simple.rs | 5 +++-- macro/src/ast.rs | 2 ++ macro/src/gen.rs | 13 +++++++++++-- macro/src/parse.rs | 19 +++++++++++++++++++ src/example.rs | 2 ++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index c0b731d..ae4adfa 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -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, - } - }, + }, + } } diff --git a/macro/src/ast.rs b/macro/src/ast.rs index 7afa732..9b8dc0f 100644 --- a/macro/src/ast.rs +++ b/macro/src/ast.rs @@ -15,6 +15,8 @@ pub(crate) struct Input { pub(crate) enum Node { Internal { doc: Vec, + /// Attributes that are used as specified and not interpreted by us. + attrs: Vec, name: syn::Ident, children: Vec, }, diff --git a/macro/src/gen.rs b/macro/src/gen.rs index fb5a090..38a9fd3 100644 --- a/macro/src/gen.rs +++ b/macro/src/gen.rs @@ -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 } diff --git a/macro/src/parse.rs b/macro/src/parse.rs index 939b1a2..bee2bd9 100644 --- a/macro/src/parse.rs +++ b/macro/src/parse.rs @@ -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::()?)) @@ -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) -> Result, Error> { Ok(out) } +/// Extracts all attributes with a path contained in `names`. +fn extract_attrs(names: &[&str], attrs: &mut Vec) -> Vec { + 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, diff --git a/src/example.rs b/src/example.rs index aefdac3..abc4715 100644 --- a/src/example.rs +++ b/src/example.rs @@ -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"]