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
use syn::visit::{self, Visit};
use syn::{Attribute, Field, Ident, Meta, NestedMeta};

const SLOG_ATTRIBUTE: &str = "slog";

/// Looks for the bare attribute, `#[slog($name)]`.
pub fn contains_named_attr(attrs: &[Attribute], name: &str) -> bool {
    slog_attributes(attrs)
        .into_iter()
        .any(|meta| meta.path().is_ident(name))
}

/// Get the contents of all `#[slog(...)]` attributes.
pub fn slog_attributes(attrs: &[Attribute]) -> Vec<Meta> {
    attrs
        .iter()
        .filter_map(|attr| attr.parse_meta().ok())
        .filter_map(|meta| match meta {
            Meta::List(list) => Some(list),
            _ => None,
        })
        .filter(|meta_list| meta_list.path.is_ident(SLOG_ATTRIBUTE))
        .flat_map(|ml| ml.nested)
        .filter_map(|nested| match nested {
            NestedMeta::Meta(m) => Some(m),
            _ => None,
        })
        .collect()
}

#[derive(Debug, Default)]
pub struct CollectFields {
    pub fields: Vec<Ident>,
}

impl<'a> Visit<'a> for CollectFields {
    fn visit_field(&mut self, field: &Field) {
        let name = field
            .ident
            .as_ref()
            .expect("You can't use this derive on a tuple struct");

        if !contains_named_attr(&field.attrs, "skip") {
            self.fields.push(name.clone());
        }

        visit::visit_field(self, field);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use syn::DeriveInput;

    #[test]
    fn find_the_skip_attribute() {
        let src = "#[slog(skip)] struct Foo {}";
        let foo: DeriveInput = syn::parse_str(src).unwrap();
        let attrs = &foo.attrs;

        assert!(contains_named_attr(attrs, "skip"));
    }
}