generate site map constant
This commit is contained in:
1 parent
502d670dff
commit
d0d7e88a0d
3 files changed
+183
-3
No files matched your search
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user