forked from yewstack/yew
/
switch.rs
180 lines (166 loc) · 6.48 KB
/
switch.rs
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use crate::switch::shadow::{ShadowCaptureVariant, ShadowMatcherToken};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{Data, DeriveInput, Fields, Ident, Variant};
mod attribute;
mod enum_impl;
mod shadow;
mod struct_impl;
mod switch_impl;
use self::{attribute::AttrToken, switch_impl::SwitchImpl};
use crate::switch::{enum_impl::EnumInner, struct_impl::StructInner};
use yew_router_route_parser::FieldNamingScheme;
/// Holds data that is required to derive Switch for a struct or a single enum variant.
pub struct SwitchItem {
pub matcher: Vec<ShadowMatcherToken>,
pub ident: Ident,
pub fields: Fields,
}
pub fn switch_impl(input: DeriveInput) -> syn::Result<TokenStream> {
let ident: Ident = input.ident;
let generics = input.generics;
Ok(match input.data {
Data::Struct(ds) => {
let field_naming_scheme = match ds.fields {
Fields::Unnamed(_) => FieldNamingScheme::Unnamed,
Fields::Unit => FieldNamingScheme::Unit,
Fields::Named(_) => FieldNamingScheme::Named,
};
let matcher = AttrToken::convert_attributes_to_tokens(input.attrs)?
.into_iter()
.enumerate()
.map(|(index, at)| at.into_shadow_matcher_tokens(index, field_naming_scheme))
.flatten()
.collect::<Vec<_>>();
let item = SwitchItem {
matcher,
ident: ident.clone(), // TODO make SwitchItem take references instead.
fields: ds.fields,
};
SwitchImpl {
target_ident: &ident,
generics: &generics,
inner: StructInner {
from_route_part: struct_impl::FromRoutePart(&item),
build_route_section: struct_impl::BuildRouteSection {
switch_item: &item,
item: &Ident::new("self", Span::call_site()),
},
},
}
.to_token_stream()
}
Data::Enum(de) => {
let switch_variants = de
.variants
.into_iter()
.map(|variant: Variant| {
let field_type = match variant.fields {
Fields::Unnamed(_) => yew_router_route_parser::FieldNamingScheme::Unnamed,
Fields::Unit => FieldNamingScheme::Unit,
Fields::Named(_) => yew_router_route_parser::FieldNamingScheme::Named,
};
let matcher = AttrToken::convert_attributes_to_tokens(variant.attrs)?
.into_iter()
.enumerate()
.map(|(index, at)| at.into_shadow_matcher_tokens(index, field_type))
.flatten()
.collect::<Vec<_>>();
Ok(SwitchItem {
matcher,
ident: variant.ident,
fields: variant.fields,
})
})
.collect::<syn::Result<Vec<_>>>()?;
SwitchImpl {
target_ident: &ident,
generics: &generics,
inner: EnumInner {
from_route_part: enum_impl::FromRoutePart {
switch_variants: &switch_variants,
enum_ident: &ident,
},
build_route_section: enum_impl::BuildRouteSection {
switch_items: &switch_variants,
enum_ident: &ident,
match_item: &Ident::new("self", Span::call_site()),
},
},
}
.to_token_stream()
}
Data::Union(_du) => panic!("Deriving FromCaptures not supported for Unions."),
})
}
trait Flatten<T> {
/// Because flatten is a nightly feature. I'm making a new variant of the function here for
/// stable use. The naming is changed to avoid this getting clobbered when object_flattening
/// 60258 is stabilized.
fn flatten_stable(self) -> Option<T>;
}
impl<T> Flatten<T> for Option<Option<T>> {
fn flatten_stable(self) -> Option<T> {
match self {
None => None,
Some(v) => v,
}
}
}
fn build_matcher_from_tokens(tokens: &[ShadowMatcherToken]) -> TokenStream {
quote! {
let settings = ::yew_router::matcher::MatcherSettings {
case_insensitive: true,
};
let matcher = ::yew_router::matcher::RouteMatcher {
tokens: ::std::vec![#(#tokens),*],
settings
};
}
}
/// Enum indicating which sort of writer is needed.
pub(crate) enum FieldType {
Named,
Unnamed { index: usize },
Unit,
}
/// This assumes that the variant/struct has been destructured.
fn write_for_token(token: &ShadowMatcherToken, naming_scheme: FieldType) -> TokenStream {
match token {
ShadowMatcherToken::Exact(lit) => {
quote! {
write!(buf, "{}", #lit).unwrap();
}
}
ShadowMatcherToken::Capture(capture) => match naming_scheme {
FieldType::Named | FieldType::Unit => match &capture {
ShadowCaptureVariant::Named(name)
| ShadowCaptureVariant::ManyNamed(name)
| ShadowCaptureVariant::NumberedNamed { name, .. } => {
let name = Ident::new(&name, Span::call_site());
quote! {
state = state.or_else(|| #name.build_route_section(buf));
}
}
ShadowCaptureVariant::Unnamed
| ShadowCaptureVariant::ManyUnnamed
| ShadowCaptureVariant::NumberedUnnamed { .. } => {
panic!("Unnamed matcher sections not allowed for named field types")
}
},
FieldType::Unnamed { index } => {
let name = unnamed_field_index_item(index);
quote! {
state = state.or_else(|| #name.build_route_section(&mut buf));
}
}
},
ShadowMatcherToken::End => quote! {},
}
}
/// Creates an ident used for destructuring unnamed fields.
///
/// There needs to be a unified way to "mangle" the unnamed fields so they can be destructured,
fn unnamed_field_index_item(index: usize) -> Ident {
Ident::new(&format!("__field_{}", index), Span::call_site())
}