Refactor macros crate
This commit is contained in:
parent
3f8a6dfee3
commit
526414cd4b
|
@ -1,20 +1,16 @@
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
mod case;
|
use proc_macro2::{Literal, Span, TokenStream};
|
||||||
mod de;
|
|
||||||
mod ser;
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
|
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::token::Colon2;
|
|
||||||
use syn::{parse_macro_input, DeriveInput, Generics};
|
use syn::{parse_macro_input, DeriveInput, Generics};
|
||||||
|
|
||||||
|
mod case;
|
||||||
use case::RenameRule;
|
use case::RenameRule;
|
||||||
|
mod de;
|
||||||
|
mod meta;
|
||||||
|
use meta::{meta_items, MetaItem, Namespace, NamespaceMeta};
|
||||||
|
mod ser;
|
||||||
|
|
||||||
#[proc_macro_derive(ToXml, attributes(xml))]
|
#[proc_macro_derive(ToXml, attributes(xml))]
|
||||||
pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
@ -220,465 +216,6 @@ impl VariantMeta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct NamespaceMeta {
|
|
||||||
uri: Option<Namespace>,
|
|
||||||
prefixes: BTreeMap<String, Namespace>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NamespaceMeta {
|
|
||||||
fn from_tokens(group: Group) -> Self {
|
|
||||||
let mut new = NamespaceMeta::default();
|
|
||||||
let mut state = NsState::Start;
|
|
||||||
for tree in group.stream() {
|
|
||||||
state = match (state, tree) {
|
|
||||||
(NsState::Start, TokenTree::Literal(lit)) => {
|
|
||||||
new.uri = Some(Namespace::Literal(lit));
|
|
||||||
NsState::Comma
|
|
||||||
}
|
|
||||||
(NsState::Start, TokenTree::Punct(punct)) if punct.as_char() == ':' => {
|
|
||||||
NsState::Path {
|
|
||||||
colon1: Some(punct),
|
|
||||||
colon2: None,
|
|
||||||
path: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(NsState::Start, TokenTree::Ident(id)) => NsState::Path {
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(syn::Path::from(id)),
|
|
||||||
},
|
|
||||||
(NsState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => {
|
|
||||||
NsState::Prefix
|
|
||||||
}
|
|
||||||
(
|
|
||||||
NsState::Path {
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
TokenTree::Punct(punct),
|
|
||||||
) if punct.as_char() == ':' => NsState::Path {
|
|
||||||
colon1: Some(punct),
|
|
||||||
colon2: None,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
(
|
|
||||||
NsState::Path {
|
|
||||||
colon1: colon1 @ Some(_),
|
|
||||||
colon2: None,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
TokenTree::Punct(punct),
|
|
||||||
) if punct.as_char() == ':' => NsState::Path {
|
|
||||||
colon1,
|
|
||||||
colon2: Some(punct),
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
(
|
|
||||||
NsState::Path {
|
|
||||||
colon1: Some(colon1),
|
|
||||||
colon2: Some(colon2),
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
TokenTree::Ident(id),
|
|
||||||
) => {
|
|
||||||
let path = match path {
|
|
||||||
Some(mut path) => {
|
|
||||||
path.segments.push(syn::PathSegment::from(id));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut segments = Punctuated::new();
|
|
||||||
segments.push_value(id.into());
|
|
||||||
|
|
||||||
syn::Path {
|
|
||||||
leading_colon: Some(Colon2 {
|
|
||||||
spans: [colon1.span(), colon2.span()],
|
|
||||||
}),
|
|
||||||
segments,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
NsState::Path {
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
NsState::Path {
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(path),
|
|
||||||
},
|
|
||||||
TokenTree::Punct(punct),
|
|
||||||
) if punct.as_char() == ',' => {
|
|
||||||
new.uri = Some(Namespace::Path(path));
|
|
||||||
NsState::Prefix
|
|
||||||
}
|
|
||||||
(
|
|
||||||
NsState::Path {
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(path),
|
|
||||||
},
|
|
||||||
TokenTree::Punct(punct),
|
|
||||||
) if punct.as_char() == '=' => {
|
|
||||||
if path.leading_colon.is_some() {
|
|
||||||
panic!("prefix cannot be defined on a path in xml attribute");
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.segments.len() != 1 {
|
|
||||||
panic!("prefix key must be a single identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
let segment = path.segments.into_iter().next().unwrap();
|
|
||||||
if !segment.arguments.is_empty() {
|
|
||||||
panic!("prefix key must be a single identifier without arguments");
|
|
||||||
}
|
|
||||||
|
|
||||||
NsState::PrefixValue {
|
|
||||||
prefix: segment.ident,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(NsState::Prefix, TokenTree::Ident(id)) => NsState::Eq { prefix: id },
|
|
||||||
(NsState::Eq { prefix }, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
|
||||||
NsState::PrefixValue { prefix }
|
|
||||||
}
|
|
||||||
(NsState::PrefixValue { prefix }, TokenTree::Literal(lit)) => {
|
|
||||||
new.prefixes
|
|
||||||
.insert(prefix.to_string(), Namespace::Literal(lit));
|
|
||||||
NsState::Comma
|
|
||||||
}
|
|
||||||
(NsState::PrefixValue { prefix }, TokenTree::Punct(punct))
|
|
||||||
if punct.as_char() == ':' =>
|
|
||||||
{
|
|
||||||
NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: Some(punct),
|
|
||||||
colon2: None,
|
|
||||||
path: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(NsState::PrefixValue { prefix }, TokenTree::Ident(id)) => NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(syn::Path::from(id)),
|
|
||||||
},
|
|
||||||
(
|
|
||||||
NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
TokenTree::Punct(punct),
|
|
||||||
) if punct.as_char() == ':' => NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: Some(punct),
|
|
||||||
colon2: None,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
(
|
|
||||||
NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: colon1 @ Some(_),
|
|
||||||
colon2: None,
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
TokenTree::Punct(punct),
|
|
||||||
) if punct.as_char() == ':' => NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1,
|
|
||||||
colon2: Some(punct),
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
(
|
|
||||||
NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: Some(colon1),
|
|
||||||
colon2: Some(colon2),
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
TokenTree::Ident(id),
|
|
||||||
) => {
|
|
||||||
let path = match path {
|
|
||||||
Some(mut path) => {
|
|
||||||
path.segments.push(syn::PathSegment::from(id));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut segments = Punctuated::new();
|
|
||||||
segments.push_value(id.into());
|
|
||||||
|
|
||||||
syn::Path {
|
|
||||||
leading_colon: Some(Colon2 {
|
|
||||||
spans: [colon1.span(), colon2.span()],
|
|
||||||
}),
|
|
||||||
segments,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(path),
|
|
||||||
},
|
|
||||||
TokenTree::Punct(punct),
|
|
||||||
) if punct.as_char() == ',' => {
|
|
||||||
new.prefixes
|
|
||||||
.insert(prefix.to_string(), Namespace::Path(path));
|
|
||||||
NsState::Prefix
|
|
||||||
}
|
|
||||||
(state, tree) => {
|
|
||||||
panic!(
|
|
||||||
"invalid state transition while parsing ns in xml attribute ({}, {tree})",
|
|
||||||
state.name()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match state {
|
|
||||||
NsState::Start | NsState::Comma => {}
|
|
||||||
NsState::Path {
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(path),
|
|
||||||
} => {
|
|
||||||
new.uri = Some(Namespace::Path(path));
|
|
||||||
}
|
|
||||||
NsState::PrefixPath {
|
|
||||||
prefix,
|
|
||||||
colon1: None,
|
|
||||||
colon2: None,
|
|
||||||
path: Some(path),
|
|
||||||
} => {
|
|
||||||
new.prefixes
|
|
||||||
.insert(prefix.to_string(), Namespace::Path(path));
|
|
||||||
}
|
|
||||||
state => panic!("invalid ns end state in xml attribute ({})", state.name()),
|
|
||||||
}
|
|
||||||
|
|
||||||
new
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
|
|
||||||
let mut items = Vec::new();
|
|
||||||
let attr = match attrs.iter().find(|attr| attr.path.is_ident("xml")) {
|
|
||||||
Some(attr) => attr,
|
|
||||||
None => return items,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut iter = attr.tokens.clone().into_iter();
|
|
||||||
let first = match iter.next() {
|
|
||||||
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
|
|
||||||
group.stream()
|
|
||||||
}
|
|
||||||
_ => panic!("expected parenthesized group in xml attribute"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if iter.next().is_some() {
|
|
||||||
panic!("expected single token tree in xml attribute");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state = MetaState::Start;
|
|
||||||
for tree in first {
|
|
||||||
let span = tree.span();
|
|
||||||
state = match (state, tree) {
|
|
||||||
(MetaState::Start, TokenTree::Ident(id)) => {
|
|
||||||
if id == "attribute" {
|
|
||||||
items.push((MetaItem::Attribute, span));
|
|
||||||
MetaState::Comma
|
|
||||||
} else if id == "ns" {
|
|
||||||
MetaState::Ns
|
|
||||||
} else if id == "rename" {
|
|
||||||
MetaState::Rename
|
|
||||||
} else if id == "rename_all" {
|
|
||||||
MetaState::RenameAll
|
|
||||||
} else if id == "scalar" {
|
|
||||||
items.push((MetaItem::Mode(Mode::Scalar), span));
|
|
||||||
MetaState::Comma
|
|
||||||
} else if id == "wrapped" {
|
|
||||||
items.push((MetaItem::Mode(Mode::Wrapped), span));
|
|
||||||
MetaState::Comma
|
|
||||||
} else if id == "serialize_with" {
|
|
||||||
MetaState::SerializeWith
|
|
||||||
} else {
|
|
||||||
panic!("unexpected key in xml attribute");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(MetaState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => {
|
|
||||||
MetaState::Start
|
|
||||||
}
|
|
||||||
(MetaState::Ns, TokenTree::Group(group))
|
|
||||||
if group.delimiter() == Delimiter::Parenthesis =>
|
|
||||||
{
|
|
||||||
items.push((MetaItem::Ns(NamespaceMeta::from_tokens(group)), span));
|
|
||||||
MetaState::Comma
|
|
||||||
}
|
|
||||||
(MetaState::Rename, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
|
||||||
MetaState::RenameValue
|
|
||||||
}
|
|
||||||
(MetaState::RenameValue, TokenTree::Literal(lit)) => {
|
|
||||||
items.push((MetaItem::Rename(lit), span));
|
|
||||||
MetaState::Comma
|
|
||||||
}
|
|
||||||
(MetaState::RenameAll, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
|
||||||
MetaState::RenameAllValue
|
|
||||||
}
|
|
||||||
(MetaState::RenameAllValue, TokenTree::Literal(lit)) => {
|
|
||||||
items.push((MetaItem::RenameAll(lit), span));
|
|
||||||
MetaState::Comma
|
|
||||||
}
|
|
||||||
(MetaState::SerializeWith, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
|
||||||
MetaState::SerializeWithValue
|
|
||||||
}
|
|
||||||
(MetaState::SerializeWithValue, TokenTree::Literal(lit)) => {
|
|
||||||
items.push((MetaItem::SerializeWith(lit), span));
|
|
||||||
MetaState::Comma
|
|
||||||
}
|
|
||||||
(state, tree) => {
|
|
||||||
panic!(
|
|
||||||
"invalid state transition while parsing xml attribute ({}, {tree})",
|
|
||||||
state.name()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum MetaState {
|
|
||||||
Start,
|
|
||||||
Comma,
|
|
||||||
Ns,
|
|
||||||
Rename,
|
|
||||||
RenameValue,
|
|
||||||
RenameAll,
|
|
||||||
RenameAllValue,
|
|
||||||
SerializeWith,
|
|
||||||
SerializeWithValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaState {
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
MetaState::Start => "Start",
|
|
||||||
MetaState::Comma => "Comma",
|
|
||||||
MetaState::Ns => "Ns",
|
|
||||||
MetaState::Rename => "Rename",
|
|
||||||
MetaState::RenameValue => "RenameValue",
|
|
||||||
MetaState::RenameAll => "RenameAll",
|
|
||||||
MetaState::RenameAllValue => "RenameAllValue",
|
|
||||||
MetaState::SerializeWith => "SerializeWith",
|
|
||||||
MetaState::SerializeWithValue => "SerializeWithValue",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NsState {
|
|
||||||
Start,
|
|
||||||
Comma,
|
|
||||||
Path {
|
|
||||||
colon1: Option<Punct>,
|
|
||||||
colon2: Option<Punct>,
|
|
||||||
path: Option<syn::Path>,
|
|
||||||
},
|
|
||||||
Prefix,
|
|
||||||
Eq {
|
|
||||||
prefix: Ident,
|
|
||||||
},
|
|
||||||
PrefixValue {
|
|
||||||
prefix: Ident,
|
|
||||||
},
|
|
||||||
PrefixPath {
|
|
||||||
prefix: Ident,
|
|
||||||
colon1: Option<Punct>,
|
|
||||||
colon2: Option<Punct>,
|
|
||||||
path: Option<syn::Path>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NsState {
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
NsState::Start => "Start",
|
|
||||||
NsState::Comma => "Comma",
|
|
||||||
NsState::Path {
|
|
||||||
colon1,
|
|
||||||
colon2,
|
|
||||||
path,
|
|
||||||
} => match (colon1, colon2, path) {
|
|
||||||
(None, None, None) => "Path [000]",
|
|
||||||
(Some(_), None, None) => "Path [100]",
|
|
||||||
(None, Some(_), None) => "Path [010]",
|
|
||||||
(None, None, Some(_)) => "Path [001]",
|
|
||||||
(Some(_), Some(_), None) => "Path [110]",
|
|
||||||
(None, Some(_), Some(_)) => "Path [011]",
|
|
||||||
(Some(_), None, Some(_)) => "Path [101]",
|
|
||||||
(Some(_), Some(_), Some(_)) => "Path [111]",
|
|
||||||
},
|
|
||||||
NsState::Prefix => "Prefix",
|
|
||||||
NsState::Eq { .. } => "Eq",
|
|
||||||
NsState::PrefixValue { .. } => "PrefixValue",
|
|
||||||
NsState::PrefixPath { .. } => "PrefixPath",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum MetaItem {
|
|
||||||
Attribute,
|
|
||||||
Ns(NamespaceMeta),
|
|
||||||
Rename(Literal),
|
|
||||||
Mode(Mode),
|
|
||||||
RenameAll(Literal),
|
|
||||||
SerializeWith(Literal),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Namespace {
|
|
||||||
Path(syn::Path),
|
|
||||||
Literal(Literal),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for Namespace {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
||||||
match self {
|
|
||||||
Namespace::Path(path) => path.to_tokens(tokens),
|
|
||||||
Namespace::Literal(lit) => lit.to_tokens(tokens),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Namespace {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Path(arg0) => f
|
|
||||||
.debug_tuple("Path")
|
|
||||||
.field(&arg0.into_token_stream().to_string())
|
|
||||||
.finish(),
|
|
||||||
Self::Literal(arg0) => f.debug_tuple("Literal").field(arg0).finish(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn discard_lifetimes(ty: &mut syn::Type) {
|
fn discard_lifetimes(ty: &mut syn::Type) {
|
||||||
match ty {
|
match ty {
|
||||||
syn::Type::Path(ty) => discard_path_lifetimes(ty),
|
syn::Type::Path(ty) => discard_path_lifetimes(ty),
|
||||||
|
|
|
@ -0,0 +1,468 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
|
||||||
|
use quote::ToTokens;
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::token::Colon2;
|
||||||
|
|
||||||
|
use super::Mode;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct NamespaceMeta {
|
||||||
|
pub(crate) uri: Option<Namespace>,
|
||||||
|
pub(crate) prefixes: BTreeMap<String, Namespace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamespaceMeta {
|
||||||
|
fn from_tokens(group: Group) -> Self {
|
||||||
|
let mut new = NamespaceMeta::default();
|
||||||
|
let mut state = NsState::Start;
|
||||||
|
for tree in group.stream() {
|
||||||
|
state = match (state, tree) {
|
||||||
|
(NsState::Start, TokenTree::Literal(lit)) => {
|
||||||
|
new.uri = Some(Namespace::Literal(lit));
|
||||||
|
NsState::Comma
|
||||||
|
}
|
||||||
|
(NsState::Start, TokenTree::Punct(punct)) if punct.as_char() == ':' => {
|
||||||
|
NsState::Path {
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(NsState::Start, TokenTree::Ident(id)) => NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(syn::Path::from(id)),
|
||||||
|
},
|
||||||
|
(NsState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => {
|
||||||
|
NsState::Prefix
|
||||||
|
}
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::Path {
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: colon1 @ Some(_),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::Path {
|
||||||
|
colon1,
|
||||||
|
colon2: Some(punct),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: Some(colon1),
|
||||||
|
colon2: Some(colon2),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Ident(id),
|
||||||
|
) => {
|
||||||
|
let path = match path {
|
||||||
|
Some(mut path) => {
|
||||||
|
path.segments.push(syn::PathSegment::from(id));
|
||||||
|
path
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut segments = Punctuated::new();
|
||||||
|
segments.push_value(id.into());
|
||||||
|
|
||||||
|
syn::Path {
|
||||||
|
leading_colon: Some(Colon2 {
|
||||||
|
spans: [colon1.span(), colon2.span()],
|
||||||
|
}),
|
||||||
|
segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ',' => {
|
||||||
|
new.uri = Some(Namespace::Path(path));
|
||||||
|
NsState::Prefix
|
||||||
|
}
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == '=' => {
|
||||||
|
if path.leading_colon.is_some() {
|
||||||
|
panic!("prefix cannot be defined on a path in xml attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.segments.len() != 1 {
|
||||||
|
panic!("prefix key must be a single identifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment = path.segments.into_iter().next().unwrap();
|
||||||
|
if !segment.arguments.is_empty() {
|
||||||
|
panic!("prefix key must be a single identifier without arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
NsState::PrefixValue {
|
||||||
|
prefix: segment.ident,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(NsState::Prefix, TokenTree::Ident(id)) => NsState::Eq { prefix: id },
|
||||||
|
(NsState::Eq { prefix }, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
||||||
|
NsState::PrefixValue { prefix }
|
||||||
|
}
|
||||||
|
(NsState::PrefixValue { prefix }, TokenTree::Literal(lit)) => {
|
||||||
|
new.prefixes
|
||||||
|
.insert(prefix.to_string(), Namespace::Literal(lit));
|
||||||
|
NsState::Comma
|
||||||
|
}
|
||||||
|
(NsState::PrefixValue { prefix }, TokenTree::Punct(punct))
|
||||||
|
if punct.as_char() == ':' =>
|
||||||
|
{
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(NsState::PrefixValue { prefix }, TokenTree::Ident(id)) => NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(syn::Path::from(id)),
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: colon1 @ Some(_),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1,
|
||||||
|
colon2: Some(punct),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: Some(colon1),
|
||||||
|
colon2: Some(colon2),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Ident(id),
|
||||||
|
) => {
|
||||||
|
let path = match path {
|
||||||
|
Some(mut path) => {
|
||||||
|
path.segments.push(syn::PathSegment::from(id));
|
||||||
|
path
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut segments = Punctuated::new();
|
||||||
|
segments.push_value(id.into());
|
||||||
|
|
||||||
|
syn::Path {
|
||||||
|
leading_colon: Some(Colon2 {
|
||||||
|
spans: [colon1.span(), colon2.span()],
|
||||||
|
}),
|
||||||
|
segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ',' => {
|
||||||
|
new.prefixes
|
||||||
|
.insert(prefix.to_string(), Namespace::Path(path));
|
||||||
|
NsState::Prefix
|
||||||
|
}
|
||||||
|
(state, tree) => {
|
||||||
|
panic!(
|
||||||
|
"invalid state transition while parsing ns in xml attribute ({}, {tree})",
|
||||||
|
state.name()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match state {
|
||||||
|
NsState::Start | NsState::Comma => {}
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
} => {
|
||||||
|
new.uri = Some(Namespace::Path(path));
|
||||||
|
}
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
} => {
|
||||||
|
new.prefixes
|
||||||
|
.insert(prefix.to_string(), Namespace::Path(path));
|
||||||
|
}
|
||||||
|
state => panic!("invalid ns end state in xml attribute ({})", state.name()),
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
let attr = match attrs.iter().find(|attr| attr.path.is_ident("xml")) {
|
||||||
|
Some(attr) => attr,
|
||||||
|
None => return items,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut iter = attr.tokens.clone().into_iter();
|
||||||
|
let first = match iter.next() {
|
||||||
|
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
|
||||||
|
group.stream()
|
||||||
|
}
|
||||||
|
_ => panic!("expected parenthesized group in xml attribute"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if iter.next().is_some() {
|
||||||
|
panic!("expected single token tree in xml attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = MetaState::Start;
|
||||||
|
for tree in first {
|
||||||
|
let span = tree.span();
|
||||||
|
state = match (state, tree) {
|
||||||
|
(MetaState::Start, TokenTree::Ident(id)) => {
|
||||||
|
if id == "attribute" {
|
||||||
|
items.push((MetaItem::Attribute, span));
|
||||||
|
MetaState::Comma
|
||||||
|
} else if id == "ns" {
|
||||||
|
MetaState::Ns
|
||||||
|
} else if id == "rename" {
|
||||||
|
MetaState::Rename
|
||||||
|
} else if id == "rename_all" {
|
||||||
|
MetaState::RenameAll
|
||||||
|
} else if id == "scalar" {
|
||||||
|
items.push((MetaItem::Mode(Mode::Scalar), span));
|
||||||
|
MetaState::Comma
|
||||||
|
} else if id == "wrapped" {
|
||||||
|
items.push((MetaItem::Mode(Mode::Wrapped), span));
|
||||||
|
MetaState::Comma
|
||||||
|
} else if id == "serialize_with" {
|
||||||
|
MetaState::SerializeWith
|
||||||
|
} else {
|
||||||
|
panic!("unexpected key in xml attribute");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(MetaState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => {
|
||||||
|
MetaState::Start
|
||||||
|
}
|
||||||
|
(MetaState::Ns, TokenTree::Group(group))
|
||||||
|
if group.delimiter() == Delimiter::Parenthesis =>
|
||||||
|
{
|
||||||
|
items.push((MetaItem::Ns(NamespaceMeta::from_tokens(group)), span));
|
||||||
|
MetaState::Comma
|
||||||
|
}
|
||||||
|
(MetaState::Rename, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
||||||
|
MetaState::RenameValue
|
||||||
|
}
|
||||||
|
(MetaState::RenameValue, TokenTree::Literal(lit)) => {
|
||||||
|
items.push((MetaItem::Rename(lit), span));
|
||||||
|
MetaState::Comma
|
||||||
|
}
|
||||||
|
(MetaState::RenameAll, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
||||||
|
MetaState::RenameAllValue
|
||||||
|
}
|
||||||
|
(MetaState::RenameAllValue, TokenTree::Literal(lit)) => {
|
||||||
|
items.push((MetaItem::RenameAll(lit), span));
|
||||||
|
MetaState::Comma
|
||||||
|
}
|
||||||
|
(MetaState::SerializeWith, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
||||||
|
MetaState::SerializeWithValue
|
||||||
|
}
|
||||||
|
(MetaState::SerializeWithValue, TokenTree::Literal(lit)) => {
|
||||||
|
items.push((MetaItem::SerializeWith(lit), span));
|
||||||
|
MetaState::Comma
|
||||||
|
}
|
||||||
|
(state, tree) => {
|
||||||
|
panic!(
|
||||||
|
"invalid state transition while parsing xml attribute ({}, {tree})",
|
||||||
|
state.name()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum MetaState {
|
||||||
|
Start,
|
||||||
|
Comma,
|
||||||
|
Ns,
|
||||||
|
Rename,
|
||||||
|
RenameValue,
|
||||||
|
RenameAll,
|
||||||
|
RenameAllValue,
|
||||||
|
SerializeWith,
|
||||||
|
SerializeWithValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaState {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
MetaState::Start => "Start",
|
||||||
|
MetaState::Comma => "Comma",
|
||||||
|
MetaState::Ns => "Ns",
|
||||||
|
MetaState::Rename => "Rename",
|
||||||
|
MetaState::RenameValue => "RenameValue",
|
||||||
|
MetaState::RenameAll => "RenameAll",
|
||||||
|
MetaState::RenameAllValue => "RenameAllValue",
|
||||||
|
MetaState::SerializeWith => "SerializeWith",
|
||||||
|
MetaState::SerializeWithValue => "SerializeWithValue",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NsState {
|
||||||
|
Start,
|
||||||
|
Comma,
|
||||||
|
Path {
|
||||||
|
colon1: Option<Punct>,
|
||||||
|
colon2: Option<Punct>,
|
||||||
|
path: Option<syn::Path>,
|
||||||
|
},
|
||||||
|
Prefix,
|
||||||
|
Eq {
|
||||||
|
prefix: Ident,
|
||||||
|
},
|
||||||
|
PrefixValue {
|
||||||
|
prefix: Ident,
|
||||||
|
},
|
||||||
|
PrefixPath {
|
||||||
|
prefix: Ident,
|
||||||
|
colon1: Option<Punct>,
|
||||||
|
colon2: Option<Punct>,
|
||||||
|
path: Option<syn::Path>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NsState {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
NsState::Start => "Start",
|
||||||
|
NsState::Comma => "Comma",
|
||||||
|
NsState::Path {
|
||||||
|
colon1,
|
||||||
|
colon2,
|
||||||
|
path,
|
||||||
|
} => match (colon1, colon2, path) {
|
||||||
|
(None, None, None) => "Path [000]",
|
||||||
|
(Some(_), None, None) => "Path [100]",
|
||||||
|
(None, Some(_), None) => "Path [010]",
|
||||||
|
(None, None, Some(_)) => "Path [001]",
|
||||||
|
(Some(_), Some(_), None) => "Path [110]",
|
||||||
|
(None, Some(_), Some(_)) => "Path [011]",
|
||||||
|
(Some(_), None, Some(_)) => "Path [101]",
|
||||||
|
(Some(_), Some(_), Some(_)) => "Path [111]",
|
||||||
|
},
|
||||||
|
NsState::Prefix => "Prefix",
|
||||||
|
NsState::Eq { .. } => "Eq",
|
||||||
|
NsState::PrefixValue { .. } => "PrefixValue",
|
||||||
|
NsState::PrefixPath { .. } => "PrefixPath",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum Namespace {
|
||||||
|
Path(syn::Path),
|
||||||
|
Literal(Literal),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Namespace {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
match self {
|
||||||
|
Namespace::Path(path) => path.to_tokens(tokens),
|
||||||
|
Namespace::Literal(lit) => lit.to_tokens(tokens),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Namespace {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Path(arg0) => f
|
||||||
|
.debug_tuple("Path")
|
||||||
|
.field(&arg0.into_token_stream().to_string())
|
||||||
|
.finish(),
|
||||||
|
Self::Literal(arg0) => f.debug_tuple("Literal").field(arg0).finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum MetaItem {
|
||||||
|
Attribute,
|
||||||
|
Ns(NamespaceMeta),
|
||||||
|
Rename(Literal),
|
||||||
|
Mode(Mode),
|
||||||
|
RenameAll(Literal),
|
||||||
|
SerializeWith(Literal),
|
||||||
|
}
|
Loading…
Reference in New Issue