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

+120 -27
View File
@@ -5,6 +5,8 @@ use syn::{Ident, LitStr};
use proc_macro2::TokenStream as TokenStream2;
use crate::nest::Layout;
use crate::nest::LayoutId;
use crate::query::QuerySegment;
use crate::segment::parse_route_segments;
use crate::segment::RouteSegment;
@@ -40,24 +42,30 @@ pub struct Route {
pub comp_name: Ident,
pub props_name: Ident,
pub route: String,
pub route_segments: Vec<RouteSegment>,
pub segments: Vec<RouteSegment>,
pub query: Option<QuerySegment>,
pub layouts: Vec<LayoutId>,
pub variant: syn::Variant,
}
impl Route {
pub fn parse(root_route: String, input: syn::Variant) -> syn::Result<Self> {
let route_attr = input
pub fn parse(
root_route: String,
layouts: Vec<LayoutId>,
variant: syn::Variant,
) -> syn::Result<Self> {
let route_attr = variant
.attrs
.iter()
.find(|attr| attr.path.is_ident("route"))
.ok_or_else(|| {
syn::Error::new_spanned(
input.clone(),
variant.clone(),
"Routable variants must have a #[route(...)] attribute",
)
})?;
let route_name = input.ident.clone();
let route_name = variant.ident.clone();
let args = route_attr.parse_args::<RouteArgs>()?;
let route = root_route + &args.route.value();
let file_based = args.comp_name.is_none();
@@ -68,53 +76,115 @@ impl Route {
.props_name
.unwrap_or_else(|| format_ident!("{}Props", comp_name));
let (route_segments, query) = parse_route_segments(&input, &route)?;
let named_fields = match &variant.fields {
syn::Fields::Named(fields) => fields,
_ => {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants must have named fields",
))
}
};
let (route_segments, query) = parse_route_segments(&variant.ident, named_fields, &route)?;
Ok(Self {
comp_name,
props_name,
route_name,
route_segments,
segments: route_segments,
route,
file_based,
query,
layouts,
variant,
})
}
pub fn display_match(&self) -> TokenStream2 {
pub fn display_match(&self, layouts: &[Layout]) -> TokenStream2 {
let name = &self.route_name;
let dynamic_segments = self.dynamic_segments();
let write_segments = self.route_segments.iter().map(|s| s.write_segment());
let dynamic_segments = self.dynamic_segments(layouts);
let write_layouts = self.layouts.iter().map(|id| layouts[id.0].write());
let write_segments = self.segments.iter().map(|s| s.write_segment());
let write_query = self.query.as_ref().map(|q| q.write());
quote! {
Self::#name { #(#dynamic_segments,)* } => {
#(#write_layouts)*
#(#write_segments)*
#write_query
}
}
}
pub fn routable_match(&self) -> TokenStream2 {
pub fn routable_match(&self, layouts: &[Layout], index: usize) -> Option<TokenStream2> {
let name = &self.route_name;
let dynamic_segments: Vec<_> = self.dynamic_segments().collect();
let props_name = &self.props_name;
let comp_name = &self.comp_name;
let dynamic_segments = self.dynamic_segments(layouts);
quote! {
Self::#name { #(#dynamic_segments,)* } => {
let comp = #props_name { #(#dynamic_segments,)* };
let cx = cx.bump().alloc(Scoped {
props: cx.bump().alloc(comp),
scope: cx,
});
#comp_name(cx)
match index.cmp(&self.layouts.len()) {
std::cmp::Ordering::Less => {
let layout = self.layouts[index];
let render_layout = layouts[layout.0].routable_match();
// This is a layout
Some(quote! {
#[allow(unused)]
Self::#name { #(#dynamic_segments,)* } => {
#render_layout
}
})
}
std::cmp::Ordering::Equal => {
let dynamic_segments_from_route = self.dynamic_segments_from_route();
let props_name = &self.props_name;
let comp_name = &self.comp_name;
// This is the final route
Some(quote! {
#[allow(unused)]
Self::#name { #(#dynamic_segments,)* } => {
let comp = #props_name { #(#dynamic_segments_from_route,)* };
let cx = cx.bump().alloc(Scoped {
props: cx.bump().alloc(comp),
scope: cx,
});
#comp_name(cx)
}
})
}
_ => None,
}
}
fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
let segments = self.route_segments.iter().filter_map(|seg| {
fn dynamic_segment_types<'a>(
&'a self,
layouts: &'a [Layout],
) -> impl Iterator<Item = TokenStream2> + 'a {
let layouts = self
.layouts
.iter()
.flat_map(|id| layouts[id.0].dynamic_segment_types());
let segments = self.segments.iter().filter_map(|seg| {
let ty = seg.ty()?;
Some(quote! {
#ty
})
});
let query = self
.query
.as_ref()
.map(|q| {
let ty = q.ty();
quote! {
#ty
}
})
.into_iter();
layouts.chain(segments.chain(query))
}
fn dynamic_segments_from_route(&self) -> impl Iterator<Item = TokenStream2> + '_ {
let segments = self.segments.iter().filter_map(|seg| {
seg.name().map(|name| {
quote! {
#name
@@ -135,8 +205,21 @@ impl Route {
segments.chain(query)
}
pub fn construct(&self, enum_name: Ident) -> TokenStream2 {
let segments = self.dynamic_segments();
fn dynamic_segments<'a>(
&'a self,
layouts: &'a [Layout],
) -> impl Iterator<Item = TokenStream2> + 'a {
let layouts = self
.layouts
.iter()
.flat_map(|id| layouts[id.0].dynamic_segments());
let dynamic_segments = self.dynamic_segments_from_route();
layouts.chain(dynamic_segments)
}
pub fn construct(&self, enum_name: Ident, layouts: &[Layout]) -> TokenStream2 {
let segments = self.dynamic_segments(layouts);
let name = &self.route_name;
quote! {
@@ -156,7 +239,7 @@ impl Route {
let mut error_variants = Vec::new();
let mut display_match = Vec::new();
for (i, segment) in self.route_segments.iter().enumerate() {
for (i, segment) in self.segments.iter().enumerate() {
let error_name = segment.error_name(i);
match segment {
RouteSegment::Static(index) => {
@@ -205,6 +288,16 @@ impl Route {
None => quote! {},
}
}
pub fn variant(&self, layouts: &[Layout]) -> TokenStream2 {
let name = &self.route_name;
let segments = self.dynamic_segments(layouts);
let types = self.dynamic_segment_types(layouts);
quote! {
#name { #(#segments: #types,)* }
}
}
}
impl ToTokens for Route {