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, } impl Parse for IdentList { fn parse(input: ParseStream) -> syn::Result { let content; let _paren_token = parenthesized!(content in input); Ok(Self { parts: Punctuated::parse_terminated(&content)?, }) } } /// Parses `("some text")` pub struct AttrInnerSingleString { _paren_token: Paren, pub content: LitStr, } impl Parse for AttrInnerSingleString { fn parse(input: ParseStream) -> syn::Result { let content; let _paren_token = parenthesized!(content in input); Ok(Self { _paren_token, content: content.parse()?, }) } } /// Parses `key="value"` pub struct KeyEqString { key: Ident, _eq_token: Token![=], lit: LitStr, } impl Parse for KeyEqString { fn parse(input: ParseStream) -> syn::Result { Ok(Self { key: input.parse()?, _eq_token: input.parse()?, lit: input.parse()?, }) } } /// Parses `(key="value", key="value", …)` pub struct AttrInnerKeyStringMap { parts: Punctuated, } impl Parse for AttrInnerKeyStringMap { fn parse(input: ParseStream) -> syn::Result { Ok(Self { parts: Punctuated::parse_terminated(input)?, }) } } impl AttrInnerKeyStringMap { pub fn multi_into_iter(iter: impl IntoIterator) -> impl Iterator)> { use std::collections::hash_map::Entry; let mut res = Vec::<(Ident, Vec)>::new(); let mut idx = HashMap::::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() } } /// Parses `(left, right)` pub struct Pair { pub first: F, pub sep: Token![,], pub second: S, } impl Parse for Pair where F: Parse, S: Parse, { fn parse(input: ParseStream) -> syn::Result { Ok(Self { first: input.parse()?, sep: input.parse()?, second: input.parse()?, }) } } /// parses `(...)` pub struct ParenthesizedTokens { pub paren: Paren, pub tokens: TokenStream, } impl Parse for ParenthesizedTokens { fn parse(input: ParseStream) -> syn::Result { let content; let paren = parenthesized!(content in input); Ok(Self { paren, tokens: content.parse()? }) } } /// parses a comma-delimeted list of `T`s with optional trailing comma pub struct SimpleCommaDelimeted(pub Vec); impl Parse for SimpleCommaDelimeted { fn parse(input: ParseStream) -> syn::Result { let punctuated = Punctuated::::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::(quote::quote! { ("a string literal") }); assert!(res.is_ok()); assert_eq!(res.ok().unwrap().content.value(), "a string literal"); let res = syn::parse2::(quote::quote! { wrong, "stuff" }); assert!(res.is_err()); } #[test] fn key_eq_string() { let res = syn::parse2::(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::(quote::quote! { wrong, "stuff" }); assert!(res.is_err()); } #[test] fn attr_inner_key_string_map() { let res = syn::parse2::(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::(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::(quote::quote! { wrong, "stuff" }); assert!(res.is_err()); } }