From 5075b4df176a8f3d1fed94fc465345fb88d4c5c4 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 29 Apr 2021 23:45:40 +0200 Subject: [PATCH] Add `derive_for_all` global attribute (defaulting to `Debug`) In most cases, I expect that users just want all structs to derive `Debug` or maybe also `Clone`. --- examples/simple.rs | 3 +- macro/src/ast.rs | 1 + macro/src/gen.rs | 11 ++------ macro/src/parse.rs | 68 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 53 insertions(+), 30 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 8b34fad..3d599c7 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -3,7 +3,8 @@ mod config { use std::path::PathBuf; confique::config! { - #[derive(Debug, Clone)] + #![derive_for_all(Debug, Clone)] + log: { /// Determines how many messages are logged. Log messages below /// this level are not emitted. Possible values: "trace", "debug", diff --git a/macro/src/ast.rs b/macro/src/ast.rs index 0b944e6..c397ddc 100644 --- a/macro/src/ast.rs +++ b/macro/src/ast.rs @@ -7,6 +7,7 @@ use proc_macro2::{Ident, TokenStream}; pub(crate) struct Input { pub(crate) root: Node, pub(crate) visibility: Option, + pub(crate) derive_for_all: Option, } /// One node in the tree of the configuration format. Can either be a leaf node diff --git a/macro/src/gen.rs b/macro/src/gen.rs index b0ff03a..39e30f9 100644 --- a/macro/src/gen.rs +++ b/macro/src/gen.rs @@ -217,19 +217,12 @@ fn gen_types(input: &Input, visibility: &TokenStream) -> TokenStream { // Main type definition let doc = &node.doc; let attrs = &node.attrs; - - // 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)] } - }; + let derives = input.derive_for_all.clone().unwrap_or(quote! { Debug }); main_types.extend(quote! { #( #[doc = #doc] )* #( #attrs )* - #derive - + #[derive( #derives )] #visibility struct #typename { #main_fields } diff --git a/macro/src/parse.rs b/macro/src/parse.rs index bd2afa6..f4aa46f 100644 --- a/macro/src/parse.rs +++ b/macro/src/parse.rs @@ -1,4 +1,5 @@ use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; use syn::{ Error, Ident, parse::{Parse, ParseStream}, @@ -13,8 +14,10 @@ use crate::ast::{Expr, Input, Leaf, Node, NodeKind, Obj}; impl Parse for Input { fn parse(input: ParseStream) -> Result { let mut outer_attrs = input.call(syn::Attribute::parse_inner)?; - let doc = extract_doc(&mut outer_attrs)?; let visibility = extract_visibility(&mut outer_attrs)?; + let derive_for_all = extract_single_list_attr("derive_for_all", &mut outer_attrs)?; + + let doc = extract_doc(&mut outer_attrs)?; let typename = extract_typename(&mut outer_attrs)?; // Extract attributes that we will just forward/emit verbatim. Well, not @@ -24,8 +27,9 @@ impl Parse for Input { a.style = syn::AttrStyle::Outer; } - assert_no_extra_attrs(&outer_attrs)?; + + // Parse children let children = input.call(>::parse_terminated)?; let root = Node { @@ -38,7 +42,7 @@ impl Parse for Input { }), }; - Ok(Self { root, visibility }) + Ok(Self { root, visibility, derive_for_all }) } } @@ -177,33 +181,57 @@ fn extract_attrs(names: &[&str], attrs: &mut Vec) -> Vec, -) -> Result, Error> { - let mut filtered = attrs.iter().filter(|attr| attr.path.is_ident(name)); - let meta = match filtered.next() { +) -> Result, Error> { + let attr = match attrs.iter().position(|attr| attr.path.is_ident(name)) { None => return Ok(None), - Some(attr) => attr.parse_meta()?, + Some(pos) => attrs.remove(pos), }; - let nv = match meta { - syn::Meta::NameValue(nv) => nv, - other => { - let msg = format!(r#"expected `name = "value"` attribute syntax for `{}`"#, name); - return Err(Error::new(other.span(), msg)); - } - }; - - if let Some(dupe) = filtered.next() { + if let Some(dupe) = attrs.iter().find(|attr| attr.path.is_ident(name)) { let msg = format!("duplicate `{}` attribute", name); return Err(Error::new(dupe.span(), msg)); } - // Remove the attribute from the vector - attrs.retain(|attr| !attr.path.is_ident(name)); + Ok(Some(attr)) +} - Ok(Some(nv.lit)) +fn extract_single_name_value_attr( + name: &str, + attrs: &mut Vec, +) -> Result, Error> { + let attr = match extract_single_attr(name, attrs)? { + None => return Ok(None), + Some(attr) => attr, + }; + + match attr.parse_meta()? { + syn::Meta::NameValue(nv) => Ok(Some(nv.lit)), + other => { + let msg = format!(r#"expected `name = "value"` attribute syntax for `{}`"#, name); + Err(Error::new(other.span(), msg)) + } + } +} + +fn extract_single_list_attr( + name: &str, + attrs: &mut Vec, +) -> Result, Error> { + let attr = match extract_single_attr(name, attrs)? { + None => return Ok(None), + Some(attr) => attr, + }; + + match attr.parse_meta()? { + syn::Meta::List(list) => Ok(Some(list.nested.to_token_stream())), + other => { + let msg = format!(r#"expected `{}(...)` attribute syntax"#, name); + return Err(Error::new(other.span(), msg)); + } + } } fn assert_string_lit(lit: syn::Lit) -> Result {