1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
#![deny(missing_docs)]
//! Derive macros for `redact_composer`. Not needed as a direct dependency.
use darling::FromDeriveInput;
use proc_macro::{self, TokenStream};
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Expr};
#[derive(FromDeriveInput, Default)]
#[darling(default, attributes(element))]
struct Opts {
name: Option<String>,
wrapped_element: Option<Expr>,
wrapped_element_doc: Option<String>,
}
/// Derives a `redact-composer` `Element` impl for this type.
///
/// The default implementation (which likely satisfies the majority of cases) is nothing more than:
/// ```ignore
/// # #[derive(Debug)]
/// # struct MyElement;
/// #[typetag::serde] // If "serde" feature enabled
/// impl Element for MyElement {}
/// ```
///
/// > *Important!: At the current time, if using the `serde` feature (enabled by default), in order
/// > to use this derive macro you need to have [`typetag`] added as a dependency to your
/// > crate.*
///
/// Additional options (if needed) are specified via the `#[element(params)]` attribute which accepts
/// any of the following params:
/// * `feature: serde`
///
/// **`name: String`:** Provides a different serialization name if you need to avoid naming collisions
/// or just prefer something different. In either case, this name is just passed along to
/// `#[typetag::serde(name = name)]`.
///
/// **Default:** the type's name.
///
/// * **`wrapped_element: Expr`:** If you are creating an Element that wraps
/// another you can specify the expression to access it (e.g. `Some(self.wrapped_item())`). The
/// expression should return an `Option<&dyn Element>`.
///
/// **Default:** `None`.
///
/// * **`wrapped_element_doc: String`:** Use this to provide a doc comment (no /// necessary) for the
/// wrapped element. Only has an effect if `wrapped_element` is also present.
#[proc_macro_derive(Element, attributes(element))]
pub fn derive(input: TokenStream) -> TokenStream {
derive_impl(quote! { ::redact_composer }, input)
}
/// See [`Element`]. This version is used if only depending on `redact_composer_core` (i.e. for
/// lib development).
#[proc_macro_derive(ElementCore, attributes(element))]
pub fn core_derive(input: TokenStream) -> TokenStream {
derive_impl(quote! { ::redact_composer_core }, input)
}
fn derive_impl(crate_path: proc_macro2::TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);
let opts = Opts::from_derive_input(&input).expect("Invalid element option");
let DeriveInput { ident, .. } = input;
let wrapped_element_comment = if let Some(comment) = opts.wrapped_element_doc {
quote! { #[doc= #comment ] }
} else {
quote! { #[doc= "Wrapped element." ] }
};
let wrapped_element_accessor = match opts.wrapped_element {
Some(accessor) => quote! {
#wrapped_element_comment
fn wrapped_element(&self) -> Option<&dyn Element> {
#accessor
}
},
None => quote! {},
};
let typetag_attr = if cfg!(feature = "serde") {
let type_tag_opts = match opts.name {
Some(name_opt) => quote! { (name = #name_opt) },
None => quote! {},
};
quote! { #[typetag::serde #type_tag_opts] }
} else {
quote! {}
};
let output = quote! {
#typetag_attr
impl #crate_path::Element for #ident {
#wrapped_element_accessor
}
};
output.into()
}