1 use std::ops::Deref;
2 use std::string::ToString;
3 
4 use syn::{Meta, NestedMeta, Path};
5 
6 use {Error, FromMeta, Result};
7 
8 /// A list of `syn::Path` instances. This type is used to extract a list of paths from an
9 /// attribute.
10 ///
11 /// # Usage
12 /// An `PathList` field on a struct implementing `FromMeta` will turn `#[builder(derive(serde::Debug, Clone))]` into:
13 ///
14 /// ```rust,ignore
15 /// StructOptions {
16 ///     derive: PathList(vec![syn::Path::new("serde::Debug"), syn::Path::new("Clone")])
17 /// }
18 /// ```
19 #[derive(Debug, Default, Clone, PartialEq, Eq)]
20 pub struct PathList(Vec<Path>);
21 
22 impl PathList {
23     /// Create a new list.
new<T: Into<Path>>(vals: Vec<T>) -> Self24     pub fn new<T: Into<Path>>(vals: Vec<T>) -> Self {
25         PathList(vals.into_iter().map(T::into).collect())
26     }
27 
28     /// Create a new `Vec` containing the string representation of each path.
to_strings(&self) -> Vec<String>29     pub fn to_strings(&self) -> Vec<String> {
30         self.0.iter().map(|p| p.segments.iter().map(|s| s.ident.to_string()).collect::<Vec<String>>().join("::")).collect()
31     }
32 }
33 
34 impl Deref for PathList {
35     type Target = Vec<Path>;
36 
deref(&self) -> &Self::Target37     fn deref(&self) -> &Self::Target {
38         &self.0
39     }
40 }
41 
42 impl From<Vec<Path>> for PathList {
from(v: Vec<Path>) -> Self43     fn from(v: Vec<Path>) -> Self {
44         PathList(v)
45     }
46 }
47 
48 impl FromMeta for PathList {
from_list(v: &[NestedMeta]) -> Result<Self>49     fn from_list(v: &[NestedMeta]) -> Result<Self> {
50         let mut paths = Vec::with_capacity(v.len());
51         for nmi in v {
52             if let NestedMeta::Meta(Meta::Path(ref path)) = *nmi {
53                 paths.push(path.clone());
54             } else {
55                 return Err(Error::unexpected_type("non-word").with_span(nmi));
56             }
57         }
58 
59         Ok(PathList(paths))
60     }
61 }
62 
63 #[cfg(test)]
64 mod tests {
65     use super::PathList;
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.parse_meta().map_err(|_| "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 paths = fm::<PathList>(quote!(ignore(Debug, Clone, Eq)));
84         assert_eq!(
85             paths.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 = PathList::from_meta(&pm(quote!(ignore(Debug, Clone = false))).unwrap());
99         let err = input.unwrap_err();
100         assert!(err.has_span());
101     }
102 }
103