| use proc_macro2::{Ident, TokenStream}; |
| use std::collections::HashMap; |
| use syn::parse::{Parse, ParseStream}; |
| use syn::punctuated::Punctuated; |
| use syn::token::Paren; |
| use syn::{LitStr, Token, parenthesized}; |
|
|
| pub struct IdentList { |
| pub parts: Punctuated<Ident, Token![,]>, |
| } |
|
|
| impl Parse for IdentList { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| let content; |
| let _paren_token = parenthesized!(content in input); |
| Ok(Self { |
| parts: Punctuated::parse_terminated(&content)?, |
| }) |
| } |
| } |
|
|
| |
| pub struct AttrInnerSingleString { |
| _paren_token: Paren, |
| pub content: LitStr, |
| } |
|
|
| impl Parse for AttrInnerSingleString { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| let content; |
| let _paren_token = parenthesized!(content in input); |
| Ok(Self { |
| _paren_token, |
| content: content.parse()?, |
| }) |
| } |
| } |
|
|
| |
| pub struct KeyEqString { |
| key: Ident, |
| _eq_token: Token![=], |
| lit: LitStr, |
| } |
|
|
| impl Parse for KeyEqString { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| Ok(Self { |
| key: input.parse()?, |
| _eq_token: input.parse()?, |
| lit: input.parse()?, |
| }) |
| } |
| } |
|
|
| |
| pub struct AttrInnerKeyStringMap { |
| parts: Punctuated<KeyEqString, Token![,]>, |
| } |
|
|
| impl Parse for AttrInnerKeyStringMap { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| Ok(Self { |
| parts: Punctuated::parse_terminated(input)?, |
| }) |
| } |
| } |
|
|
| impl AttrInnerKeyStringMap { |
| pub fn multi_into_iter(iter: impl IntoIterator<Item = Self>) -> impl Iterator<Item = (Ident, Vec<LitStr>)> { |
| use std::collections::hash_map::Entry; |
|
|
| let mut res = Vec::<(Ident, Vec<LitStr>)>::new(); |
| let mut idx = HashMap::<Ident, usize>::new(); |
|
|
| for part in iter.into_iter().flat_map(|x: Self| x.parts) { |
| match idx.entry(part.key) { |
| Entry::Occupied(occ) => { |
| res[*occ.get()].1.push(part.lit); |
| } |
| Entry::Vacant(vac) => { |
| let ident = vac.key().clone(); |
| vac.insert(res.len()); |
| res.push((ident, vec![part.lit])); |
| } |
| } |
| } |
|
|
| res.into_iter() |
| } |
| } |
|
|
| |
| pub struct Pair<F, S> { |
| pub first: F, |
| pub sep: Token![,], |
| pub second: S, |
| } |
|
|
| impl<F, S> Parse for Pair<F, S> |
| where |
| F: Parse, |
| S: Parse, |
| { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| Ok(Self { |
| first: input.parse()?, |
| sep: input.parse()?, |
| second: input.parse()?, |
| }) |
| } |
| } |
|
|
| |
| pub struct ParenthesizedTokens { |
| pub paren: Paren, |
| pub tokens: TokenStream, |
| } |
|
|
| impl Parse for ParenthesizedTokens { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| let content; |
| let paren = parenthesized!(content in input); |
| Ok(Self { paren, tokens: content.parse()? }) |
| } |
| } |
|
|
| |
| pub struct SimpleCommaDelimeted<T>(pub Vec<T>); |
|
|
| impl<T: Parse> Parse for SimpleCommaDelimeted<T> { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| let punctuated = Punctuated::<T, Token![,]>::parse_terminated(input)?; |
| Ok(Self(punctuated.into_iter().collect())) |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn attr_inner_single_string() { |
| let res = syn::parse2::<AttrInnerSingleString>(quote::quote! { |
| ("a string literal") |
| }); |
| assert!(res.is_ok()); |
| assert_eq!(res.ok().unwrap().content.value(), "a string literal"); |
|
|
| let res = syn::parse2::<AttrInnerSingleString>(quote::quote! { |
| wrong, "stuff" |
| }); |
| assert!(res.is_err()); |
| } |
|
|
| #[test] |
| fn key_eq_string() { |
| let res = syn::parse2::<KeyEqString>(quote::quote! { |
| key="value" |
| }); |
| assert!(res.is_ok()); |
| let res = res.ok().unwrap(); |
| assert_eq!(res.key, "key"); |
| assert_eq!(res.lit.value(), "value"); |
|
|
| let res = syn::parse2::<KeyEqString>(quote::quote! { |
| wrong, "stuff" |
| }); |
| assert!(res.is_err()); |
| } |
|
|
| #[test] |
| fn attr_inner_key_string_map() { |
| let res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! { |
| key="value", key2="value2" |
| }); |
| assert!(res.is_ok()); |
| let res = res.ok().unwrap(); |
| for (item, (k, v)) in res.parts.into_iter().zip(vec![("key", "value"), ("key2", "value2")]) { |
| assert_eq!(item.key, k); |
| assert_eq!(item.lit.value(), v); |
| } |
|
|
| let res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! { |
| key="value", key2="value2", |
| }); |
| assert!(res.is_ok()); |
| let res = res.ok().unwrap(); |
| for (item, (k, v)) in res.parts.into_iter().zip(vec![("key", "value"), ("key2", "value2")]) { |
| assert_eq!(item.key, k); |
| assert_eq!(item.lit.value(), v); |
| } |
|
|
| let res = syn::parse2::<AttrInnerKeyStringMap>(quote::quote! { |
| wrong, "stuff" |
| }); |
| assert!(res.is_err()); |
| } |
| } |
|
|