1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
//! Utility functions
use crate::{prelude::*, Error};
/// Parse a tagged attribute. This is very helpful for implementing [`FromAttribute`].
///
/// A tagged attribute is an attribute in the form of `#[prefix(result)]`. This function will return `Some(result)` if the `prefix` matches.
///
/// The contents of the result can be either:
/// - `ParsedAttribute::Tagged(Ident)`, e.g. `#[serde(skip)]` will be `Tagged("skip")`
/// - `ParsedAttribute::Property(Ident, lit)`, e.g. `#[bincode(crate = "foo")]` will be `Property("crate", "foo")`
///
/// # Examples
/// ```no_run
/// # use virtue::prelude::*;
/// # use std::str::FromStr;
/// # fn parse_token_stream_group(input: &'static str) -> Group {
/// # let token_stream: TokenStream = proc_macro2::TokenStream::from_str(input).unwrap().into();
/// # match token_stream.into_iter().next() {
/// # Some(TokenTree::Group(group)) => group,
/// # _ => unreachable!(),
/// # }
/// # }
/// use virtue::utils::{parse_tagged_attribute, ParsedAttribute};
///
/// // The attribute being parsed
/// let group: Group = parse_token_stream_group("#[prefix(result, foo = \"bar\")]");
///
/// let attributes = parse_tagged_attribute(&group, "prefix").unwrap().unwrap();
/// let mut iter = attributes.into_iter();
///
/// // The stream will contain the contents of the `prefix(...)`
/// match iter.next() {
/// Some(ParsedAttribute::Tag(i)) => {
/// assert_eq!(i.to_string(), String::from("result"));
/// },
/// x => panic!("Unexpected attribute: {:?}", x)
/// }
/// match iter.next() {
/// Some(ParsedAttribute::Property(key, val)) => {
/// assert_eq!(key.to_string(), String::from("foo"));
/// assert_eq!(val.to_string(), String::from("\"bar\""));
/// },
/// x => panic!("Unexpected attribute: {:?}", x)
/// }
///
/// ```
pub fn parse_tagged_attribute(group: &Group, prefix: &str) -> Result<Option<Vec<ParsedAttribute>>> {
let stream = &mut group.stream().into_iter();
if let Some(TokenTree::Ident(attribute_ident)) = stream.next() {
#[allow(clippy::cmp_owned)] // clippy is wrong
if attribute_ident.to_string() == prefix {
if let Some(TokenTree::Group(group)) = stream.next() {
let mut result = Vec::new();
let mut stream = group.stream().into_iter().peekable();
while let Some(token) = stream.next() {
match (token, stream.peek()) {
(TokenTree::Ident(key), Some(TokenTree::Punct(p)))
if p.as_char() == ',' =>
{
result.push(ParsedAttribute::Tag(key));
stream.next();
}
(TokenTree::Ident(key), None) => {
result.push(ParsedAttribute::Tag(key));
stream.next();
}
(TokenTree::Ident(key), Some(TokenTree::Punct(p)))
if p.as_char() == '=' =>
{
stream.next();
if let Some(TokenTree::Literal(lit)) = stream.next() {
result.push(ParsedAttribute::Property(key, lit));
match stream.next() {
Some(TokenTree::Punct(p)) if p.as_char() == ',' => {}
None => {}
x => {
return Err(Error::custom_at_opt_token("Expected `,`", x));
}
}
}
}
(x, _) => {
return Err(Error::custom_at(
"Expected `key` or `key = \"val\"`",
x.span(),
));
}
}
}
return Ok(Some(result));
}
}
}
Ok(None)
}
#[derive(Clone, Debug)]
#[non_exhaustive]
/// A parsed attribute. See [`parse_tagged_attribute`] for more information.
pub enum ParsedAttribute {
/// A tag, created by parsing `#[prefix(foo)]`
Tag(Ident),
/// A property, created by parsing `#[prefix(foo = "bar")]`
Property(Ident, Literal),
}
#[test]
fn test_parse_tagged_attribute() {
let group: Group = match crate::token_stream("[prefix(result, foo = \"bar\", baz)]").next() {
Some(TokenTree::Group(group)) => group,
x => panic!("Unexpected token {:?}", x),
};
let attributes = parse_tagged_attribute(&group, "prefix").unwrap().unwrap();
let mut iter = attributes.into_iter();
// The stream will contain the contents of the `prefix(...)`
match iter.next() {
Some(ParsedAttribute::Tag(i)) => {
assert_eq!(i.to_string(), String::from("result"));
}
x => panic!("Unexpected attribute: {:?}", x),
}
match iter.next() {
Some(ParsedAttribute::Property(key, val)) => {
assert_eq!(key.to_string(), String::from("foo"));
assert_eq!(val.to_string(), String::from("\"bar\""));
}
x => panic!("Unexpected attribute: {:?}", x),
}
match iter.next() {
Some(ParsedAttribute::Tag(i)) => {
assert_eq!(i.to_string(), String::from("baz"));
}
x => panic!("Unexpected attribute: {:?}", x),
}
}