implement layouts and outlets
This commit is contained in:
1 parent
f4227c7311
commit
fbd333e334
10 files changed
+910
-259
No files matched your search
@@ -1,23 +1,25 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use nest::Nest;
|
||||
use nest::{Layout, Nest};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{__private::Span, format_ident, quote, ToTokens};
|
||||
use route::Route;
|
||||
use route_tree::RouteTreeSegment;
|
||||
use syn::{parse_macro_input, Ident};
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
use crate::{nest::LayoutId, route_tree::RouteTree};
|
||||
|
||||
mod nest;
|
||||
mod query;
|
||||
mod route;
|
||||
mod route_tree;
|
||||
mod segment;
|
||||
|
||||
#[proc_macro_derive(Routable, attributes(route, nest, end_nest))]
|
||||
pub fn derive_routable(input: TokenStream) -> TokenStream {
|
||||
let routes_enum = parse_macro_input!(input as syn::DeriveInput);
|
||||
// #[proc_macro_derive(Routable, attributes(route, nest, end_nest))]
|
||||
#[proc_macro_attribute]
|
||||
pub fn routable(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let routes_enum = parse_macro_input!(input as syn::ItemEnum);
|
||||
|
||||
let route_enum = match RouteEnum::parse(routes_enum) {
|
||||
Ok(route_enum) => route_enum,
|
||||
@@ -44,59 +46,104 @@ pub fn derive_routable(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
struct RouteEnum {
|
||||
route_name: Ident,
|
||||
vis: syn::Visibility,
|
||||
attrs: Vec<syn::Attribute>,
|
||||
name: Ident,
|
||||
routes: Vec<Route>,
|
||||
layouts: Vec<Layout>,
|
||||
}
|
||||
|
||||
impl RouteEnum {
|
||||
fn parse(input: syn::DeriveInput) -> syn::Result<Self> {
|
||||
let name = &input.ident;
|
||||
fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
|
||||
let name = &data.ident;
|
||||
|
||||
if let syn::Data::Enum(data) = input.data {
|
||||
let mut routes = Vec::new();
|
||||
enum NestRef {
|
||||
Static(String),
|
||||
Dynamic { id: LayoutId },
|
||||
}
|
||||
|
||||
let mut current_base_route = Vec::new();
|
||||
let mut routes = Vec::new();
|
||||
|
||||
for variant in data.variants {
|
||||
// Apply the any nesting attributes in order
|
||||
for attr in &variant.attrs {
|
||||
if attr.path.is_ident("nest") {
|
||||
let nest: Nest = attr.parse_args()?;
|
||||
match nest {
|
||||
Nest::Static(s) => current_base_route.push(s),
|
||||
_ => todo!(),
|
||||
let mut layouts = Vec::new();
|
||||
|
||||
let mut nest_stack = Vec::new();
|
||||
|
||||
for variant in data.variants {
|
||||
// Apply the any nesting attributes in order
|
||||
for attr in &variant.attrs {
|
||||
if attr.path.is_ident("nest") {
|
||||
let nest: Nest = attr.parse_args()?;
|
||||
let nest_ref = match nest {
|
||||
Nest::Static(s) => NestRef::Static(s),
|
||||
Nest::Layout(mut l) => {
|
||||
// if there is a static nest before this, add it to the layout
|
||||
let mut static_prefix = nest_stack
|
||||
.iter()
|
||||
// walk backwards and take all static nests
|
||||
.rev()
|
||||
.map_while(|nest| match nest {
|
||||
NestRef::Static(s) => Some(s.clone()),
|
||||
NestRef::Dynamic { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// reverse the static prefix so it is in the correct order
|
||||
static_prefix.reverse();
|
||||
|
||||
if !static_prefix.is_empty() {
|
||||
l.add_static_prefix(&static_prefix.join("/"));
|
||||
}
|
||||
|
||||
let id = layouts.len();
|
||||
layouts.push(l);
|
||||
NestRef::Dynamic { id: LayoutId(id) }
|
||||
}
|
||||
} else if attr.path.is_ident("end_nest") {
|
||||
current_base_route.pop();
|
||||
}
|
||||
};
|
||||
nest_stack.push(nest_ref);
|
||||
} else if attr.path.is_ident("end_nest") {
|
||||
nest_stack.pop();
|
||||
}
|
||||
|
||||
let route = Route::parse(current_base_route.join("/"), variant)?;
|
||||
routes.push(route);
|
||||
}
|
||||
|
||||
let myself = Self {
|
||||
route_name: name.clone(),
|
||||
routes,
|
||||
};
|
||||
let mut trailing_static_route = nest_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.map_while(|nest| match nest {
|
||||
NestRef::Static(s) => Some(s.clone()),
|
||||
NestRef::Dynamic { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
trailing_static_route.reverse();
|
||||
let active_layouts = nest_stack
|
||||
.iter()
|
||||
.filter_map(|nest| match nest {
|
||||
NestRef::Static(_) => None,
|
||||
NestRef::Dynamic { id } => Some(*id),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(myself)
|
||||
} else {
|
||||
Err(syn::Error::new_spanned(
|
||||
input.clone(),
|
||||
"Routable can only be derived for enums",
|
||||
))
|
||||
let route = Route::parse(trailing_static_route.join("/"), active_layouts, variant)?;
|
||||
routes.push(route);
|
||||
}
|
||||
|
||||
let myself = Self {
|
||||
vis: data.vis,
|
||||
attrs: data.attrs,
|
||||
name: name.clone(),
|
||||
routes,
|
||||
layouts,
|
||||
};
|
||||
|
||||
Ok(myself)
|
||||
}
|
||||
|
||||
fn impl_display(&self) -> TokenStream2 {
|
||||
let mut display_match = Vec::new();
|
||||
|
||||
for route in &self.routes {
|
||||
display_match.push(route.display_match());
|
||||
display_match.push(route.display_match(&self.layouts));
|
||||
}
|
||||
|
||||
let name = &self.route_name;
|
||||
let name = &self.name;
|
||||
|
||||
quote! {
|
||||
impl std::fmt::Display for #name {
|
||||
@@ -111,13 +158,14 @@ impl RouteEnum {
|
||||
}
|
||||
|
||||
fn parse_impl(&self) -> TokenStream2 {
|
||||
let tree = RouteTreeSegment::build(&self.routes);
|
||||
let name = &self.route_name;
|
||||
let tree = RouteTree::new(&self.routes, &self.layouts);
|
||||
let name = &self.name;
|
||||
|
||||
let error_name = format_ident!("{}MatchError", self.route_name);
|
||||
let tokens = tree
|
||||
.into_iter()
|
||||
.map(|t| t.to_tokens(self.route_name.clone(), error_name.clone()));
|
||||
let error_name = format_ident!("{}MatchError", self.name);
|
||||
let tokens = tree.roots.iter().map(|&id| {
|
||||
let route = tree.get(id).unwrap();
|
||||
route.to_tokens(&tree, self.name.clone(), error_name.clone(), &self.layouts)
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl<'a> TryFrom<&'a str> for #name {
|
||||
@@ -148,10 +196,7 @@ impl RouteEnum {
|
||||
}
|
||||
|
||||
fn error_name(&self) -> Ident {
|
||||
Ident::new(
|
||||
&(self.route_name.to_string() + "MatchError"),
|
||||
Span::call_site(),
|
||||
)
|
||||
Ident::new(&(self.name.to_string() + "MatchError"), Span::call_site())
|
||||
}
|
||||
|
||||
fn error_type(&self) -> TokenStream2 {
|
||||
@@ -164,7 +209,7 @@ impl RouteEnum {
|
||||
for route in &self.routes {
|
||||
let route_name = &route.route_name;
|
||||
|
||||
let error_name = Ident::new(&format!("{}ParseError", route_name), Span::call_site());
|
||||
let error_name = route.error_ident();
|
||||
let route_str = &route.route;
|
||||
|
||||
error_variants.push(quote! { #route_name(#error_name) });
|
||||
@@ -172,6 +217,17 @@ impl RouteEnum {
|
||||
type_defs.push(route.error_type());
|
||||
}
|
||||
|
||||
for layout in &self.layouts {
|
||||
let layout_name = &layout.layout_name;
|
||||
|
||||
let error_name = layout.error_ident();
|
||||
let route_str = &layout.route;
|
||||
|
||||
error_variants.push(quote! { #layout_name(#error_name) });
|
||||
display_match.push(quote! { Self::#layout_name(err) => write!(f, "Layout '{}' ('{}') did not match:\n{}", stringify!(#layout_name), #route_str, err)? });
|
||||
type_defs.push(layout.error_type());
|
||||
}
|
||||
|
||||
quote! {
|
||||
#(#type_defs)*
|
||||
|
||||
@@ -192,17 +248,47 @@ impl RouteEnum {
|
||||
}
|
||||
|
||||
fn routable_impl(&self) -> TokenStream2 {
|
||||
let mut routable_match = Vec::new();
|
||||
let name = &self.name;
|
||||
|
||||
for route in &self.routes {
|
||||
routable_match.push(route.routable_match());
|
||||
let mut layers = Vec::new();
|
||||
|
||||
loop {
|
||||
let index = layers.len();
|
||||
let mut routable_match = Vec::new();
|
||||
|
||||
// Collect all routes that match the current layer
|
||||
for route in &self.routes {
|
||||
if let Some(matched) = route.routable_match(&self.layouts, index) {
|
||||
routable_match.push(matched);
|
||||
}
|
||||
}
|
||||
|
||||
// All routes are exhausted
|
||||
if routable_match.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
layers.push(quote! {
|
||||
#(#routable_match)*
|
||||
});
|
||||
}
|
||||
|
||||
let index_iter = 0..layers.len();
|
||||
|
||||
quote! {
|
||||
impl Routable for Route {
|
||||
fn render<'a>(self, cx: &'a ScopeState) -> Element<'a> {
|
||||
match self {
|
||||
#(#routable_match)*
|
||||
impl Routable for #name where Self: Clone {
|
||||
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
|
||||
let myself = self.clone();
|
||||
match level {
|
||||
#(
|
||||
#index_iter => {
|
||||
match myself {
|
||||
#layers
|
||||
_ => panic!("Route::render called with invalid level {}", level),
|
||||
}
|
||||
},
|
||||
)*
|
||||
_ => panic!("Route::render called with invalid level {}", level),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,8 +299,17 @@ impl RouteEnum {
|
||||
impl ToTokens for RouteEnum {
|
||||
fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
|
||||
let routes = &self.routes;
|
||||
let vis = &self.vis;
|
||||
let name = &self.name;
|
||||
let attrs = &self.attrs;
|
||||
let variants = routes.iter().map(|r| r.variant(&self.layouts));
|
||||
|
||||
tokens.extend(quote!(
|
||||
#(#attrs)*
|
||||
#vis enum #name {
|
||||
#(#variants),*
|
||||
}
|
||||
|
||||
#[path = "pages"]
|
||||
mod pages {
|
||||
#(#routes)*
|
||||
|
||||
Reference in new issue
Block a user