generate site map constant

This commit is contained in:
Evan Almloff committed 2023-05-23 18:47:33 -05:00
1 parent 502d670dff
commit d0d7e88a0d
3 files changed
+183 -3

No files matched your search

+127 -2
View File
@@ -5,6 +5,7 @@ use nest::{Nest, NestId};
use proc_macro::TokenStream;
use quote::{__private::Span, format_ident, quote, ToTokens};
use route::Route;
use segment::RouteSegment;
use syn::{parse::ParseStream, parse_macro_input, Ident, Token};
use proc_macro2::TokenStream as TokenStream2;
@@ -51,12 +52,16 @@ struct RouteEnum {
routes: Vec<Route>,
nests: Vec<Nest>,
layouts: Vec<Layout>,
site_map: Vec<SiteMapSegment>,
}
impl RouteEnum {
fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
let name = &data.ident;
let mut site_map = Vec::new();
let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new();
let mut routes = Vec::new();
let mut layouts: Vec<Layout> = Vec::new();
@@ -106,10 +111,43 @@ impl RouteEnum {
};
let nest = attr.parse_args_with(parser)?;
// add the current segment to the site map stack
let segments: Vec<_> = nest
.segments
.iter()
.map(|seg| {
let segment_type = seg.into();
SiteMapSegment {
segment_type,
children: Vec::new(),
}
})
.collect();
if !segments.is_empty() {
site_map_stack.push(segments);
}
nests.push(nest);
nest_stack.push(NestId(nest_index));
} else if attr.path.is_ident("end_nest") {
nest_stack.pop();
// pop the current nest segment off the stack and add it to the parent or the site map
if let Some(segment) = site_map_stack.pop() {
let children = site_map_stack
.last_mut()
.map(|seg| &mut seg.last_mut().unwrap().children)
.unwrap_or(&mut site_map);
// Turn the list of segments in the segments stack into a tree
let mut iter = segment.into_iter().rev();
let mut current = iter.next().unwrap();
for mut segment in iter {
segment.children.push(current);
current = segment;
}
children.push(current);
}
} else if attr.path.is_ident("layout") {
let parser = |input: ParseStream| {
let bang: Option<Token![!]> = input.parse().ok();
@@ -149,6 +187,16 @@ impl RouteEnum {
let route = Route::parse(active_nests, active_layouts, variant.clone())?;
// add the route to the site map
if let Some(segment) = SiteMapSegment::new(&route.segments) {
let parent = site_map_stack.last_mut();
let children = match parent {
Some(parent) => &mut parent.last_mut().unwrap().children,
None => &mut site_map,
};
children.push(segment);
}
routes.push(route);
}
@@ -157,6 +205,7 @@ impl RouteEnum {
routes,
nests,
layouts,
site_map,
};
Ok(myself)
@@ -276,6 +325,7 @@ impl RouteEnum {
fn routable_impl(&self) -> TokenStream2 {
let name = &self.name;
let site_map = &self.site_map;
let mut layers = Vec::new();
@@ -304,6 +354,10 @@ impl RouteEnum {
quote! {
impl dioxus_router::routable::Routable for #name where Self: Clone {
const SITE_MAP: &'static [dioxus_router::routable::SiteMapSegment] = &[
#(#site_map,)*
];
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
let myself = self.clone();
match level {
@@ -311,12 +365,10 @@ impl RouteEnum {
#index_iter => {
match myself {
#layers
// _ => panic!("Route::render called with invalid level {}", level),
_ => None
}
},
)*
// _ => panic!("Route::render called with invalid level {}", level),
_ => None
}
}
@@ -339,3 +391,76 @@ impl ToTokens for RouteEnum {
));
}
}
struct SiteMapSegment {
pub segment_type: SegmentType,
pub children: Vec<SiteMapSegment>,
}
impl SiteMapSegment {
fn new(segments: &[RouteSegment]) -> Option<Self> {
let mut current = None;
// walk backwards through the new segments, adding children as we go
for segment in segments.iter().rev() {
let segment_type = segment.into();
let mut segment = SiteMapSegment {
segment_type,
children: Vec::new(),
};
// if we have a current segment, add it as a child
if let Some(current) = current.take() {
segment.children.push(current)
}
current = Some(segment);
}
current
}
}
impl ToTokens for SiteMapSegment {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let segment_type = &self.segment_type;
let children = &self.children;
tokens.extend(quote! {
dioxus_router::routable::SiteMapSegment {
segment_type: #segment_type,
children: &[
#(#children,)*
]
}
});
}
}
enum SegmentType {
Static(String),
Dynamic(String),
CatchAll(String),
}
impl ToTokens for SegmentType {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
SegmentType::Static(s) => {
tokens.extend(quote! { dioxus_router::routable::SegmentType::Static(#s) })
}
SegmentType::Dynamic(s) => {
tokens.extend(quote! { dioxus_router::routable::SegmentType::Dynamic(#s) })
}
SegmentType::CatchAll(s) => {
tokens.extend(quote! { dioxus_router::routable::SegmentType::CatchAll(#s) })
}
}
}
}
impl<'a> From<&'a RouteSegment> for SegmentType {
fn from(value: &'a RouteSegment) -> Self {
match value {
segment::RouteSegment::Static(s) => SegmentType::Static(s.to_string()),
segment::RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()),
segment::RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()),
}
}
}
+1
View File
@@ -47,6 +47,7 @@ pub mod prelude {
pub use crate::components::*;
pub use crate::contexts::*;
pub use crate::hooks::*;
pub use crate::routable::*;
pub use crate::router_cfg::RouterConfiguration;
pub use dioxus_router_macro::Routable;
}
+55 -1
View File
@@ -3,7 +3,7 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use std::str::FromStr;
use std::{fmt::Display, str::FromStr};
/// An error that occurs when parsing a route
#[derive(Debug, PartialEq)]
@@ -95,6 +95,9 @@ impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
/// Something that can be routed to
pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
/// The error that can occur when parsing a route
const SITE_MAP: &'static [SiteMapSegment];
/// Render the route at the given level
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
}
@@ -124,3 +127,54 @@ where
self.render(cx, level)
}
}
/// A type erased map of the site structurens
#[derive(Debug, Clone, PartialEq)]
pub struct SiteMapSegment {
/// The type of the route segment
pub segment_type: SegmentType,
/// The children of the route segment
pub children: &'static [SiteMapSegment],
}
impl SiteMapSegment {
/// Take a map of the site structure and flatten it into a vector of routes
pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
let mut routes = Vec::new();
self.flatten_inner(&mut routes, Vec::new());
routes
}
fn flatten_inner(&self, routes: &mut Vec<Vec<SegmentType>>, current: Vec<SegmentType>) {
let mut current = current;
current.push(self.segment_type.clone());
if self.children.is_empty() {
routes.push(current);
} else {
for child in self.children {
child.flatten_inner(routes, current.clone());
}
}
}
}
/// The type of a route segment
#[derive(Debug, Clone, PartialEq)]
pub enum SegmentType {
/// A static route segment
Static(&'static str),
/// A dynamic route segment
Dynamic(&'static str),
/// A catch all route segment
CatchAll(&'static str),
}
impl Display for SegmentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
SegmentType::Static(s) => write!(f, "/{}", s),
SegmentType::Dynamic(s) => write!(f, "/:{}", s),
SegmentType::CatchAll(s) => write!(f, "/:...{}", s),
}
}
}