implement layouts and outlets

This commit is contained in:
Evan Almloff committed 2023-05-20 16:32:48 -05:00
1 parent f4227c7311
commit fbd333e334
10 files changed
+910 -259

No files matched your search

+150 -55
View File
@@ -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)*