Skip to content
This repository has been archived by the owner on Jul 19, 2020. It is now read-only.

State Attribute #218

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/yew_router_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ mod switch;
/// }
/// ```
/// Check out the examples directory in the repository to see some more usages of the routing syntax.
#[proc_macro_derive(Switch, attributes(to, rest, end))]
#[proc_macro_derive(Switch, attributes(to, rest, end, state))]
pub fn switch(tokens: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(tokens as DeriveInput);

Expand All @@ -100,3 +100,8 @@ pub fn rest(_: TokenStream, _: TokenStream) -> TokenStream {
pub fn end(_: TokenStream, _: TokenStream) -> TokenStream {
TokenStream::new()
}

#[proc_macro_attribute]
pub fn state(_: TokenStream, _: TokenStream) -> TokenStream {
TokenStream::new()
}
22 changes: 22 additions & 0 deletions crates/yew_router_macro/src/switch/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,25 @@ impl AttrToken {
}
}
}


pub fn get_attr_strings(attributes: Vec<Attribute>) -> impl Iterator<Item=String> {
attributes
.into_iter()
.filter_map(|attr: Attribute| attr.parse_meta().ok())
.filter_map(|meta: Meta| match meta {
Meta::NameValue(mnv) => mnv
.path
.clone()
.get_ident()
.into_iter()
.map(|ident| ident.to_string())
.next(),
Meta::Path(path) => path
.get_ident()
.into_iter()
.map(|ident| ident.to_string())
.next(),
Meta::List(_) => None
})
}
168 changes: 88 additions & 80 deletions crates/yew_router_macro/src/switch/struct_impl/from_route_part.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::switch::SwitchItem;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{Field, Fields, Type};
use crate::switch::attribute::get_attr_strings;


pub struct FromRoutePart<'a>(pub &'a SwitchItem);
Expand Down Expand Up @@ -36,55 +37,21 @@ impl<'a> ToTokens for FromRoutePart<'a> {
fn build_struct_from_captures(ident: &Ident, fields: &Fields) -> TokenStream {
match fields {
Fields::Named(named_fields) => {
let (field_declarations, fields): (Vec<_>, Vec<_>) = named_fields
let fields: Vec<NamedField> = named_fields
.named
.iter()
.filter_map(|field: &Field| {
let field_ty: &Type = &field.ty;
field.ident.as_ref().map(|i| {
let key = i.to_string();
(i, key, field_ty)
let is_state = get_attr_strings(field.attrs.clone()).any(|s| s.as_str() == "state");
field.ident.as_ref().map(|field_name| {
let key = field_name.to_string();
NamedField{field_name, key, field_ty, is_state}
})
})
.map(|(field_name, key, field_ty): (&Ident, String, &Type)| {
let field_decl = quote! {
let #field_name = {
let (v, s) = match captures.remove(#key) {
::std::option::Option::Some(value) => {
<#field_ty as ::yew_router::Switch>::from_route_part(
value,
state,
)
}
::std::option::Option::None => {
(
<#field_ty as ::yew_router::Switch>::key_not_available(),
state,
)
}
};
match v {
::std::option::Option::Some(val) => {
state = s; // Set state for the next var.
val
},
::std::option::Option::None => return (::std::option::Option::None, s) // Failed
}
};
};

(field_decl, field_name)
})
.unzip();

quote! {
if let ::std::option::Option::Some(mut captures) = matcher
.capture_route_into_map(&route_string)
.ok()
.map(|x| x.1)
{
#(#field_declarations)*
.collect();

return quote! {
if let ::std::option::Option::Some(mut captures) = matcher.capture_route_into_map(&route_string).ok().map(|x| x.1) {
return (
::std::option::Option::Some(
#ident {
Expand All @@ -93,52 +60,42 @@ fn build_struct_from_captures(ident: &Ident, fields: &Fields) -> TokenStream {
),
state
);
}
}
};
};
}
Fields::Unnamed(unnamed_fields) => {
let (field_declarations, fields): (Vec<_>, Vec<_>) = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(idx, f)| {
let field_ty = &f.ty;
let field_var_name = Ident::new(&format!("field_{}", idx), Span::call_site());
let field_decl = quote! {
let #field_var_name = {
let (v, s) = match drain.next() {
::std::option::Option::Some(value) => {
<#field_ty as ::yew_router::Switch>::from_route_part(
value,
state,
)
},
::std::option::Option::None => {
(
<#field_ty as ::yew_router::Switch>::key_not_available(),
state,
)
}
};
match v {
::std::option::Option::Some(val) => {
state = s; // Set state for the next var.
val
},
::std::option::Option::None => return (::std::option::Option::None, s) // Failed
let fields = unnamed_fields.unnamed.iter().map(|f: &Field| {
let field_ty = &f.ty;
quote! {
{
let (v, s) = match drain.next() {
::std::option::Option::Some(value) => {
<#field_ty as ::yew_router::Switch>::from_route_part(
value,
state,
)
},
::std::option::Option::None => {
(
<#field_ty as ::yew_router::Switch>::key_not_available(),
state,
)
}
};
};

(field_decl, field_var_name)
})
.unzip();
match v {
::std::option::Option::Some(val) => {
state = s; // Set state for the next var.
val
},
::std::option::Option::None => return (::std::option::Option::None, s) // Failed
}
}
}
});

quote! {
if let Some(mut captures) = matcher.capture_route_into_vec(&route_string).ok().map(|x| x.1) {
let mut drain = captures.drain(..);
#(#field_declarations)*

return (
::std::option::Option::Some(
#ident(
Expand All @@ -161,3 +118,54 @@ fn build_struct_from_captures(ident: &Ident, fields: &Fields) -> TokenStream {
}
}
}


pub struct NamedField<'a> {
field_name: &'a Ident,
key: String,
field_ty: &'a Type,
is_state: bool
}

impl <'a> ToTokens for NamedField<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let NamedField {
field_name, key, field_ty, is_state
} = self;
// If the named field is marked as state, then just take the state,
// otherwise try to match the key in the captures group
if *is_state {
tokens.extend(quote!{
#field_name: {
state.take().expect("Can't take twice")
}
})
} else {
tokens.extend(quote! {
#field_name: {
let (v, s) = match captures.remove(#key) {
::std::option::Option::Some(value) => {
<#field_ty as ::yew_router::Switch>::from_route_part(
value,
state,
)
}
::std::option::Option::None => {
(
<#field_ty as ::yew_router::Switch>::key_not_available(),
state,
)
}
};
match v {
::std::option::Option::Some(val) => {
state = s; // Set state for the next var.
val
},
::std::option::Option::None => return (::std::option::Option::None, s) // Failed
}
}
})
}
}
}
22 changes: 22 additions & 0 deletions tests/macro_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,28 @@ mod tests {
)
}


#[test]
fn state_minimal() {
#[derive(Debug, Switch, Clone, PartialEq)]
#[to = "/hello"]
pub struct Test {
#[state]
blob: String
}
let route = Route {
route: "/hello".to_string(),
state: "lorem ipsum".to_string()
};
let switched = Test::switch(route).expect("should produce item");
assert_eq!(
switched,
Test {
blob: "lorem ipsum".to_string()
}
)
}

mod fragment {
use super::*;

Expand Down