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