From b56a7748c08ee3627309838fbd510426ed655718 Mon Sep 17 00:00:00 2001 From: Henry Zimmerman Date: Sun, 29 Dec 2019 13:00:46 -0500 Subject: [PATCH] initial work on attribute to set state --- crates/yew_router_macro/src/lib.rs | 7 +- .../yew_router_macro/src/switch/attribute.rs | 22 +++++ .../src/switch/struct_impl/from_route_part.rs | 88 ++++++++++++------- tests/macro_test/src/lib.rs | 22 +++++ 4 files changed, 107 insertions(+), 32 deletions(-) diff --git a/crates/yew_router_macro/src/lib.rs b/crates/yew_router_macro/src/lib.rs index 4f407bf..3387f76 100644 --- a/crates/yew_router_macro/src/lib.rs +++ b/crates/yew_router_macro/src/lib.rs @@ -76,7 +76,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 { crate::switch::switch_impl(tokens) } @@ -95,3 +95,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() +} diff --git a/crates/yew_router_macro/src/switch/attribute.rs b/crates/yew_router_macro/src/switch/attribute.rs index 7d8dd11..4bd2f18 100644 --- a/crates/yew_router_macro/src/switch/attribute.rs +++ b/crates/yew_router_macro/src/switch/attribute.rs @@ -77,3 +77,25 @@ impl AttrToken { } } } + + +pub fn get_attr_strings(attributes: Vec) -> impl Iterator { + 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 + }) +} \ No newline at end of file diff --git a/crates/yew_router_macro/src/switch/struct_impl/from_route_part.rs b/crates/yew_router_macro/src/switch/struct_impl/from_route_part.rs index bc12538..312d1bd 100644 --- a/crates/yew_router_macro/src/switch/struct_impl/from_route_part.rs +++ b/crates/yew_router_macro/src/switch/struct_impl/from_route_part.rs @@ -4,6 +4,7 @@ use crate::switch::SwitchItem; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{Field, Fields, Type}; +use crate::switch::attribute::get_attr_strings; pub struct FromRoutePart<'a>(pub &'a SwitchItem); @@ -38,43 +39,17 @@ impl<'a> ToTokens for FromRoutePart<'a> { fn build_struct_from_captures(ident: &Ident, fields: &Fields) -> TokenStream2 { match fields { Fields::Named(named_fields) => { - let fields: Vec = named_fields + let fields: Vec = 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)| { - 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 - } - } - } - }) .collect(); return quote! { @@ -145,3 +120,54 @@ fn build_struct_from_captures(ident: &Ident, fields: &Fields) -> TokenStream2 { } } } + + +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 + } + } + }) + } + } +} \ No newline at end of file diff --git a/tests/macro_test/src/lib.rs b/tests/macro_test/src/lib.rs index 8df83a9..eb294a0 100644 --- a/tests/macro_test/src/lib.rs +++ b/tests/macro_test/src/lib.rs @@ -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::*;