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
102
103
104
mod convert;
mod fielddef;
mod fns;
mod input;
mod lint;

use heck::{ShoutySnekCase, SnekCase};
use input::{DEFAULT_VALUE_FIELD, RENAME_FIELD};
use macro_compose::{Collector, Context};
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::format_ident;
use syn::{DeriveInput, Field, LitStr};

#[proc_macro_derive(MacroInput, attributes(macro_input))]
/// automatically derive `TryFrom<&[syn::Attribute]>` and `fn strip(attrs: &mut Vec<syn::Attribute>)`
///
/// supported types:
/// - `u8`, `i32`, `f32`, `char`, `bool`, `String` or `Vec<u8>` for parsing literals eg `#[foo(bar = 3)]`
/// - `Option<u8>`, `Option<i32>`, `Option<f32>`, `Option<char>`, `Option<bool>`, `Option<String>` or `Option<Vec<u8>>` for optionally parsing literals eg either `#[foo(bar = 3)]` or nothing
/// - `Option<()>` for parsing flags eg `#[foo(bar)]`
/// - `Vec<Ident>` and `Option<Vec<Ident>>` for parsing idents eg `#[foo(bar(baz, qux))]`
///
/// paths get converted to lower_snake unless `rename` is specified
///
/// use `#[macro_input]` for customization:
/// - `rename` to rename either the path or field name eg `#[macro_input(rename = "some_name")]`
/// - `default_value` for default values eg `#[macro_input(default_value = "some literal")]` (this is not supported for idents)
/// # Example
/// ```
/// use macro_input_macros::MacroInput;
/// use std::convert::TryFrom;
/// use syn::{parse_quote, Attribute};
///
/// #[derive(MacroInput, PartialEq, Debug)]
/// pub struct SomeInput {
///     pub flag: Option<()>,
///     pub optional: Option<i32>,
///     #[macro_input(default_value = 3)]
///     pub with_default: i32,
///     pub required: i32,
/// }
///
/// #[derive(MacroInput, PartialEq, Debug)]
/// #[macro_input(rename = "YetAnotherName")]
/// pub struct OtherInput {
///     #[macro_input(rename = "new_name")]
///     pub renamed: i32,
/// }
///
/// # fn main() -> syn::Result<()> {
/// // construct some attributes
/// let attr1: Attribute = parse_quote!(#[some_input(flag, required = 5)]);
/// let attr2: Attribute = parse_quote!(#[some_input(optional = 8, with_default = 4)]);
/// let attr3: Attribute = parse_quote!(#[YetAnotherName(new_name = 6)]);
///
/// // parse SomeInput with only some attributes
/// let input1 = SomeInput::try_from(&[attr1.clone()] as &[Attribute])?;
/// // parse SomeInput with all attributes
/// let input2 = SomeInput::try_from(&[attr1, attr2] as &[Attribute])?;
/// // parse OtherInput
/// let input3 = OtherInput::try_from(&[attr3] as &[Attribute])?;
///
/// assert_eq!(input1, SomeInput { flag: Some(()), optional: None, with_default: 3, required: 5 });
/// assert_eq!(input2, SomeInput { flag: Some(()), optional: Some(8), with_default: 4, required: 5 });
/// assert_eq!(input3, OtherInput { renamed: 6 });
/// # Ok(())
/// # }
/// ```

pub fn derive_macro_input(item: TokenStream) -> TokenStream {
    let mut collector = Collector::new();
    let mut ctx = Context::<DeriveInput>::new_parse(&mut collector, item);

    // lint
    ctx.lint(&input::STRUCT_LINT);
    ctx.lint(&fielddef::Name);
    ctx.lint(&fielddef::FieldType);
    ctx.lint(&lint::Name);

    // expand
    ctx.expand(&convert::TryFromAttributes);
    ctx.expand(&fielddef::ConstFields);
    ctx.expand(&fns::Strip);

    collector.finish().into()
}

fn mod_name(input: &DeriveInput) -> Ident {
    let path = input.ident.to_string().to_snek_case();
    format_ident!("__{}", &*path, span = input.ident.span())
}

fn field_name(f: &Field) -> (String, Ident) {
    let (name, span) = RENAME_FIELD.get::<LitStr>(&f.attrs).unwrap().map_or_else(
        || {
            let ident = f.ident.as_ref().unwrap();
            (ident.to_string(), ident.span())
        },
        |s| (s.value(), s.span()),
    );
    let field_name = format!("{}_field", name).TO_SHOUTY_SNEK_CASE();
    (name, Ident::new(&*field_name, span))
}