|
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()); |
|
} |
|
} |
|
|