1 use std::ops::Deref;
2 use std::string::ToString;
3 
4 use syn::{Ident, Meta, NestedMeta};
5 
6 use {Error, FromMeta, Result};
7 
8 /// A list of `syn::Ident` instances. This type is used to extract a list of words from an
9 /// attribute.
10 ///
11 /// # Usage
12 /// An `IdentList` field on a struct implementing `FromMeta` will turn `#[builder(derive(Debug, Clone))]` into:
13 ///
14 /// ```rust,ignore
15 /// StructOptions {
16 ///     derive: IdentList(vec![syn::Ident::new("Debug"), syn::Ident::new("Clone")])
17 /// }
18 /// ```
19 #[derive(Debug, Default, Clone, PartialEq, Eq)]
20 pub struct IdentList(Vec<Ident>);
21 
22 impl IdentList {
23     /// Create a new list.
new<T: Into<Ident>>(vals: Vec<T>) -> Self24     pub fn new<T: Into<Ident>>(vals: Vec<T>) -> Self {
25         IdentList(vals.into_iter().map(T::into).collect())
26     }
27 
28     /// Create a new `Vec` containing the string representation of each ident.
to_strings(&self) -> Vec<String>29     pub fn to_strings(&self) -> Vec<String> {
30         self.0.iter().map(ToString::to_string).collect()
31     }
32 }
33 
34 impl Deref for IdentList {
35     type Target = Vec<Ident>;
36 
deref(&self) -> &Self::Target37     fn deref(&self) -> &Self::Target {
38         &self.0
39     }
40 }
41 
42 impl From<Vec<Ident>> for IdentList {
from(v: Vec<Ident>) -> Self43     fn from(v: Vec<Ident>) -> Self {
44         IdentList(v)
45     }
46 }
47 
48 impl FromMeta for IdentList {
from_list(v: &[NestedMeta]) -> Result<Self>49     fn from_list(v: &[NestedMeta]) -> Result<Self> {
50         let mut idents = Vec::with_capacity(v.len());
51         for nmi in v {
52             if let NestedMeta::Meta(Meta::Word(ref ident)) = *nmi {
53                 idents.push(ident.clone());
54             } else {
55                 return Err(Error::unexpected_type("non-word").with_span(nmi));
56             }
57         }
58 
59         Ok(IdentList(idents))
60     }
61 }
62 
63 #[cfg(test)]
64 mod tests {
65     use super::IdentList;
66     use proc_macro2::TokenStream;
67     use syn::{Attribute, Meta};
68     use FromMeta;
69 
70     /// parse a string as a syn::Meta instance.
pm(tokens: TokenStream) -> ::std::result::Result<Meta, String>71     fn pm(tokens: TokenStream) -> ::std::result::Result<Meta, String> {
72         let attribute: Attribute = parse_quote!(#[#tokens]);
73         attribute.interpret_meta().ok_or("Unable to parse".into())
74     }
75 
fm<T: FromMeta>(tokens: TokenStream) -> T76     fn fm<T: FromMeta>(tokens: TokenStream) -> T {
77         FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
78             .expect("Tests should pass valid input")
79     }
80 
81     #[test]
succeeds()82     fn succeeds() {
83         let idents = fm::<IdentList>(quote!(ignore(Debug, Clone, Eq)));
84         assert_eq!(
85             idents.to_strings(),
86             vec![
87                 String::from("Debug"),
88                 String::from("Clone"),
89                 String::from("Eq")
90             ]
91         );
92     }
93 
94     /// Check that the parser rejects non-word members of the list, and that the error
95     /// has an associated span.
96     #[test]
fails_non_word()97     fn fails_non_word() {
98         let input = IdentList::from_meta(&pm(quote!(ignore(Debug, Clone = false))).unwrap());
99         let err = input.unwrap_err();
100         assert!(err.has_span());
101     }
102 }
103