get basic routing working

This commit is contained in:
Greg Johnston 2024-03-06 13:41:40 -05:00
parent 9f02cc8cc1
commit 84ebdc1b92
9 changed files with 408 additions and 85 deletions

View file

@ -12,7 +12,7 @@ use leptos::{
use log::{debug, info}; use log::{debug, info};
use routing::{ use routing::{
location::{BrowserUrl, Location}, location::{BrowserUrl, Location},
NestedRoute, Router, Routes, StaticSegment, NestedRoute, ParamSegment, Router, Routes, StaticSegment,
}; };
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -28,6 +28,25 @@ pub fn RouterExample() -> impl IntoView {
let router = Router::new( let router = Router::new(
BrowserUrl::new().unwrap(), BrowserUrl::new().unwrap(),
Routes::new(( Routes::new((
NestedRoute {
segments: StaticSegment(""),
children: (
NestedRoute {
segments: ParamSegment("id"),
children: (),
data: (),
view: Contact,
},
NestedRoute {
segments: StaticSegment(""),
children: (),
data: (),
view: || "Select a contact.",
},
),
data: (),
view: ContactList,
},
NestedRoute { NestedRoute {
segments: StaticSegment("settings"), segments: StaticSegment("settings"),
children: (), children: (),
@ -122,19 +141,28 @@ pub fn ContactRoutes() -> impl IntoView {
</Route> </Route>
} }
}*/ }*/
/*
#[component] #[component]
pub fn ContactList() -> impl IntoView { pub fn ContactList() -> impl IntoView {
log::debug!("rendering <ContactList/>"); info!("rendering <ContactList/>");
// contexts are passed down through the route tree // contexts are passed down through the route tree
provide_context(ExampleContext(42)); provide_context(ExampleContext(42));
on_cleanup(|| { Owner::on_cleanup(|| {
info!("cleaning up <ContactList/>"); info!("cleaning up <ContactList/>");
}); });
let location = use_location(); view! {
<div class="contact-list">
<h1>"Contacts"</h1>
<li><a href="/1">1</a></li>
<li><a href="/2">2</a></li>
<li><a href="/3">3</a></li>
</div>
}
/*let location = use_location();
let contacts = create_resource(move || location.search.get(), get_contacts); let contacts = create_resource(move || location.search.get(), get_contacts);
let contacts = move || { let contacts = move || {
contacts.get().map(|contacts| { contacts.get().map(|contacts| {
@ -162,14 +190,14 @@ pub fn ContactList() -> impl IntoView {
intro="fadeIn" intro="fadeIn"
/> />
</div> </div>
} }*/
}*/ }
/* /*
#[derive(Params, PartialEq, Clone, Debug)] #[derive(Params, PartialEq, Clone, Debug)]
pub struct ContactParams { pub struct ContactParams {
// Params isn't implemented for usize, only Option<usize> // Params isn't implemented for usize, only Option<usize>
id: Option<usize>, id: Option<usize>,
} }*/
#[component] #[component]
pub fn Contact() -> impl IntoView { pub fn Contact() -> impl IntoView {
@ -180,11 +208,17 @@ pub fn Contact() -> impl IntoView {
use_context::<ExampleContext>() use_context::<ExampleContext>()
); );
on_cleanup(|| { Owner::on_cleanup(|| {
info!("cleaning up <Contact/>"); info!("cleaning up <Contact/>");
}); });
let params = use_params::<ContactParams>(); view! {
<div class="contact">
<h2>"Contact"</h2>
</div>
}
/* let params = use_params::<ContactParams>();
let contact = create_resource( let contact = create_resource(
move || { move || {
params params
@ -229,8 +263,8 @@ pub fn Contact() -> impl IntoView {
{contact_display} {contact_display}
</Transition> </Transition>
</div> </div>
} }*/
}*/ }
#[component] #[component]
pub fn About() -> impl IntoView { pub fn About() -> impl IntoView {
@ -271,16 +305,14 @@ pub fn Settings() -> impl IntoView {
}); });
view! { view! {
<> <h1>"Settings"</h1>
<h1>"Settings"</h1> <form>
<form> <fieldset>
<fieldset> <legend>"Name"</legend>
<legend>"Name"</legend> <input type="text" name="first_name" placeholder="First"/>
<input type="text" name="first_name" placeholder="First"/> <input type="text" name="last_name" placeholder="Last"/>
<input type="text" name="last_name" placeholder="Last"/> </fieldset>
</fieldset> <pre>"This page is just a placeholder."</pre>
<pre>"This page is just a placeholder."</pre> </form>
</form>
</>
} }
} }

View file

@ -6,13 +6,9 @@ use tracing_subscriber_wasm::MakeConsoleWriter;
pub fn main() { pub fn main() {
fmt() fmt()
.with_writer( .with_writer(
// To avoide trace events in the browser from showing their
// JS backtrace, which is very annoying, in my opinion
MakeConsoleWriter::default() MakeConsoleWriter::default()
.map_trace_level_to(tracing::Level::DEBUG), .map_trace_level_to(tracing::Level::DEBUG),
) )
// For some reason, if we don't do this in the browser, we get
// a runtime error.
.without_time() .without_time()
.init(); .init();
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();

View file

@ -38,6 +38,18 @@ impl Owner {
} }
} }
pub fn child(&self) -> Self {
let parent = Some(Arc::downgrade(&self.inner));
Self {
inner: Arc::new(RwLock::new(OwnerInner {
parent,
nodes: Default::default(),
contexts: Default::default(),
cleanups: Default::default(),
})),
}
}
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T { pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
let prev = { let prev = {
OWNER.with(|o| { OWNER.with(|o| {

View file

@ -13,6 +13,7 @@ url = "2"
js-sys = { version = "0.3" } js-sys = { version = "0.3" }
wasm-bindgen = { version = "0.2" } wasm-bindgen = { version = "0.2" }
tracing = { version = "0.1", optional = true } tracing = { version = "0.1", optional = true }
paste = "1.0.14"
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3" version = "0.3"
@ -41,3 +42,6 @@ features = [
"RequestMode", "RequestMode",
"Response", "Response",
] ]
[features]
tracing = ["dep:tracing", "routing_utils/tracing"]

View file

@ -1,13 +1,14 @@
use crate::{generate_route_list::RouteList, location::Location}; use crate::{generate_route_list::RouteList, location::Location, Params};
use core::marker::PhantomData; use core::marker::PhantomData;
use either_of::Either; use either_of::*;
use reactive_graph::{ use reactive_graph::{
computed::ArcMemo, computed::ArcMemo,
effect::RenderEffect, effect::RenderEffect,
owner::Owner,
traits::{Read, Track}, traits::{Read, Track},
}; };
use routing_utils::{ use routing_utils::{
MatchInterface, MatchNestedRoutes, PossibleRouteMatch, Routes, MatchInterface, MatchNestedRoutes, PossibleRouteMatch, RouteMatchId, Routes,
}; };
use std::borrow::Cow; use std::borrow::Cow;
use tachys::{ use tachys::{
@ -16,8 +17,8 @@ use tachys::{
renderer::Renderer, renderer::Renderer,
ssr::StreamBuilder, ssr::StreamBuilder,
view::{ view::{
add_attr::AddAnyAttr, either::EitherState, Position, PositionState, add_attr::AddAnyAttr, either::EitherState, Mountable, Position,
Render, RenderHtml, PositionState, Render, RenderHtml,
}, },
}; };
@ -125,7 +126,11 @@ where
Rndr: Renderer + 'static, Rndr: Renderer + 'static,
{ {
type State = RenderEffect< type State = RenderEffect<
EitherState<View::State, <Fallback as Render<Rndr>>::State, Rndr>, EitherState<
NestedRouteState<View::State>,
<Fallback as Render<Rndr>>::State,
Rndr,
>,
>; >;
type FallibleState = (); type FallibleState = ();
@ -136,27 +141,47 @@ where
let url = url.clone(); let url = url.clone();
move |_| url.read().path().to_string() move |_| url.read().path().to_string()
}); });
let search_parans = ArcMemo::new({ let search_params = ArcMemo::new({
let url = url.clone(); let url = url.clone();
move |_| url.read().search_params().clone() move |_| url.read().search_params().clone()
}); });
RenderEffect::new(move |prev| { let outer_owner =
tachys::dom::log(&format!("recalculating route")); Owner::current().expect("creating Router, but no Owner was found");
RenderEffect::new(move |prev: Option<EitherState<_, _, _>>| {
let path = path.read(); let path = path.read();
let new_view = match self.routes.match_route(&*path) { let new_match = self.routes.match_route(&path);
Some(matched) => {
let view = matched.to_view();
let view = view.choose();
Either::Left(view)
}
_ => Either::Right((self.fallback)()),
};
if let Some(mut prev) = prev { if let Some(mut prev) = prev {
new_view.rebuild(&mut prev); if let Some(new_match) = new_match {
match &mut prev.state {
Either::Left(prev) => {
nested_rebuild(&outer_owner, prev, new_match);
}
Either::Right(_) => {
Either::<_, Fallback>::Left(NestedRouteView::new(
&outer_owner,
new_match,
))
.rebuild(&mut prev);
}
}
} else {
Either::<NestedRouteView<View>, _>::Right((self
.fallback)(
))
.rebuild(&mut prev);
}
prev prev
} else { } else {
new_view.build() match new_match {
Some(matched) => Either::Left(NestedRouteView::new(
&outer_owner,
matched,
)),
_ => Either::Right((self.fallback)()),
}
.build()
} }
}) })
} }
@ -175,6 +200,142 @@ where
} }
} }
fn nested_rebuild<'a, NewMatch, R>(
outer_owner: &Owner,
current: &mut NestedRouteState<
<<NewMatch::View as ChooseView>::Output as Render<R>>::State,
>,
new: NewMatch,
) where
NewMatch: MatchInterface<'a>,
NewMatch::View: ChooseView,
<NewMatch::View as ChooseView>::Output: Render<R>,
R: Renderer,
{
// if the new match is a different branch of the nested route tree from the current one, we can
// just rebuild the view starting here: everything underneath it will change
if new.as_id() != current.id {
// TODO provide params + matched via context?
let new_view = NestedRouteView::new(&outer_owner, new);
let prev_owner = std::mem::replace(&mut current.owner, new_view.owner);
current.id = new_view.id;
current.params = new_view.params;
current.matched = new_view.matched;
current
.owner
.with(|| new_view.view.rebuild(&mut current.view));
// TODO is this the right place to drop the old Owner?
drop(prev_owner);
} else {
tracing::warn!("TODO: replace");
// otherwise, we should recurse to the children of the current view, and the new match
//nested_rebuild(current.as_child_mut(), new.as_child())
}
// update params, in case they're different
// TODO
}
pub struct NestedRouteView<View> {
id: RouteMatchId,
owner: Owner,
params: Params,
matched: String,
view: View,
}
impl<View> NestedRouteView<View> {
pub fn new<'a, Matcher>(outer_owner: &Owner, matched: Matcher) -> Self
where
Matcher: MatchInterface<'a>,
Matcher::View: ChooseView<Output = View>,
{
NestedRouteView {
id: matched.as_id(),
owner: outer_owner.child(),
params: matched
.to_params()
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
matched: matched.as_matched().to_string(),
view: matched.to_view().choose(),
}
}
}
impl<R, View> Render<R> for NestedRouteView<View>
where
View: Render<R>,
R: Renderer,
{
type State = NestedRouteState<View::State>;
type FallibleState = ();
fn build(self) -> Self::State {
let NestedRouteView {
id,
owner,
params,
matched,
view,
} = self;
NestedRouteState {
id,
owner,
params,
matched,
view: view.build(),
}
}
fn rebuild(self, state: &mut Self::State) {
todo!()
}
fn try_build(self) -> tachys::error::Result<Self::FallibleState> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> tachys::error::Result<()> {
todo!()
}
}
pub struct NestedRouteState<ViewState> {
id: RouteMatchId,
owner: Owner,
params: Params,
matched: String,
view: ViewState,
}
impl<ViewState, R> Mountable<R> for NestedRouteState<ViewState>
where
ViewState: Mountable<R>,
R: Renderer,
{
fn unmount(&mut self) {
self.view.unmount();
}
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
self.view.mount(parent, marker);
}
fn insert_before_this(
&self,
parent: &R::Element,
child: &mut dyn Mountable<R>,
) -> bool {
self.view.insert_before_this(parent, child)
}
}
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> RenderHtml<Rndr> impl<Rndr, Loc, FallbackFn, Fallback, Children, View> RenderHtml<Rndr>
for Router<Rndr, Loc, Children, FallbackFn> for Router<Rndr, Loc, Children, FallbackFn>
where where
@ -239,6 +400,40 @@ where
self self
} }
} }
macro_rules! tuples {
($either:ident => $($ty:ident),*) => {
paste::paste! {
impl<$($ty, [<Fn $ty>],)*> ChooseView for $either<$([<Fn $ty>],)*>
where
$([<Fn $ty>]: Fn() -> $ty,)*
{
type Output = $either<$($ty,)*>;
fn choose(self) -> Self::Output {
match self {
$($either::$ty(f) => $either::$ty(f()),)*
}
}
}
}
}
}
tuples!(EitherOf3 => A, B, C);
tuples!(EitherOf4 => A, B, C, D);
tuples!(EitherOf5 => A, B, C, D, E);
tuples!(EitherOf6 => A, B, C, D, E, F);
tuples!(EitherOf7 => A, B, C, D, E, F, G);
tuples!(EitherOf8 => A, B, C, D, E, F, G, H);
tuples!(EitherOf9 => A, B, C, D, E, F, G, H, I);
tuples!(EitherOf10 => A, B, C, D, E, F, G, H, I, J);
tuples!(EitherOf11 => A, B, C, D, E, F, G, H, I, J, K);
tuples!(EitherOf12 => A, B, C, D, E, F, G, H, I, J, K, L);
tuples!(EitherOf13 => A, B, C, D, E, F, G, H, I, J, K, L, M);
tuples!(EitherOf14 => A, B, C, D, E, F, G, H, I, J, K, L, M, N);
tuples!(EitherOf15 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
tuples!(EitherOf16 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
/* /*
impl<Rndr, Loc, Fal, Children> RenderHtml<Rndr> impl<Rndr, Loc, Fal, Children> RenderHtml<Rndr>
for Router<Rndr, Loc, Children, Fal> for Router<Rndr, Loc, Children, Fal>

View file

@ -60,12 +60,16 @@ where
}; };
let (matched, remaining) = self.children.match_nested(path); let (matched, remaining) = self.children.match_nested(path);
#[cfg(feature = "tracing")]
tracing::info!("matched = {:?}", matched.is_some());
let matched = matched?; let matched = matched?;
if !remaining.is_empty() { if !remaining.is_empty() {
#[cfg(feature = "tracing")]
tracing::info!("did not match because remaining was {remaining:?}");
None None
} else { } else {
Some(matched) Some(matched.1)
} }
} }
@ -79,11 +83,18 @@ where
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct RouteMatchId(pub(crate) u8);
pub trait MatchInterface<'a> { pub trait MatchInterface<'a> {
type Params: IntoIterator<Item = (&'a str, &'a str)>; type Params: IntoIterator<Item = (&'a str, &'a str)>;
type Child; type Child;
type View; type View;
fn as_id(&self) -> RouteMatchId;
fn as_matched(&self) -> &str;
fn to_params(&self) -> Self::Params; fn to_params(&self) -> Self::Params;
fn to_child(&'a self) -> Self::Child; fn to_child(&'a self) -> Self::Child;
@ -95,7 +106,10 @@ pub trait MatchNestedRoutes<'a> {
type Data; type Data;
type Match: MatchInterface<'a>; type Match: MatchInterface<'a>;
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str); fn match_nested(
&'a self,
path: &'a str,
) -> (Option<(RouteMatchId, Self::Match)>, &'a str);
fn generate_routes( fn generate_routes(
&self, &self,

View file

@ -1,7 +1,7 @@
use super::{ use super::{
MatchInterface, MatchNestedRoutes, PartialPathMatch, PossibleRouteMatch, MatchInterface, MatchNestedRoutes, PartialPathMatch, PossibleRouteMatch,
}; };
use crate::PathSegment; use crate::{PathSegment, RouteMatchId};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::{fmt, iter}; use core::{fmt, iter};
@ -17,6 +17,7 @@ pub struct NestedRoute<Segments, Children, Data, View> {
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub struct NestedMatch<'a, ParamsIter, Child, View> { pub struct NestedMatch<'a, ParamsIter, Child, View> {
id: RouteMatchId,
/// The portion of the full path matched only by this nested route. /// The portion of the full path matched only by this nested route.
matched: &'a str, matched: &'a str,
/// The map of params matched only by this nested route. /// The map of params matched only by this nested route.
@ -51,6 +52,14 @@ where
type Child = &'a Child; type Child = &'a Child;
type View = &'a View; type View = &'a View;
fn as_id(&self) -> RouteMatchId {
self.id
}
fn as_matched(&self) -> &str {
self.matched
}
fn to_params(&self) -> Self::Params { fn to_params(&self) -> Self::Params {
self.params.clone() self.params.clone()
} }
@ -87,7 +96,10 @@ where
<<Children::Match as MatchInterface<'a>>::Params as IntoIterator>::IntoIter, <<Children::Match as MatchInterface<'a>>::Params as IntoIterator>::IntoIter,
>, Children::Match, View>; >, Children::Match, View>;
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) { fn match_nested(
&'a self,
path: &'a str,
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
self.segments self.segments
.test(path) .test(path)
.and_then( .and_then(
@ -98,17 +110,21 @@ where
}| { }| {
let (inner, remaining) = let (inner, remaining) =
self.children.match_nested(remaining); self.children.match_nested(remaining);
let inner = inner?; let (id, inner) = inner?;
let params = params.into_iter(); let params = params.into_iter();
if remaining.is_empty() { if remaining.is_empty() {
Some(( Some((
Some(NestedMatch { Some((
matched, id,
params: params.chain(inner.to_params()), NestedMatch {
child: inner, id,
view: &self.view, matched,
}), params: params.chain(inner.to_params()),
child: inner,
view: &self.view,
},
)),
remaining, remaining,
)) ))
} else { } else {

View file

@ -1,4 +1,4 @@
use crate::{MatchInterface, MatchNestedRoutes, PathSegment}; use crate::{MatchInterface, MatchNestedRoutes, PathSegment, RouteMatchId};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::iter; use core::iter;
use either_of::*; use either_of::*;
@ -8,6 +8,14 @@ impl<'a> MatchInterface<'a> for () {
type Child = (); type Child = ();
type View = (); type View = ();
fn as_id(&self) -> RouteMatchId {
RouteMatchId(0)
}
fn as_matched(&self) -> &str {
""
}
fn to_params(&self) -> Self::Params { fn to_params(&self) -> Self::Params {
iter::empty() iter::empty()
} }
@ -21,8 +29,11 @@ impl<'a> MatchNestedRoutes<'a> for () {
type Data = (); type Data = ();
type Match = (); type Match = ();
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) { fn match_nested(
(Some(()), path) &self,
path: &'a str,
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
(Some((RouteMatchId(0), ())), path)
} }
fn generate_routes( fn generate_routes(
@ -40,6 +51,14 @@ where
type Child = A::Child; type Child = A::Child;
type View = A::View; type View = A::View;
fn as_id(&self) -> RouteMatchId {
RouteMatchId(0)
}
fn as_matched(&self) -> &str {
self.0.as_matched()
}
fn to_params(&self) -> Self::Params { fn to_params(&self) -> Self::Params {
self.0.to_params() self.0.to_params()
} }
@ -60,7 +79,10 @@ where
type Data = A::Data; type Data = A::Data;
type Match = A::Match; type Match = A::Match;
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) { fn match_nested(
&'a self,
path: &'a str,
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
self.0.match_nested(path) self.0.match_nested(path)
} }
@ -83,6 +105,20 @@ where
type Child = Either<A::Child, B::Child>; type Child = Either<A::Child, B::Child>;
type View = Either<A::View, B::View>; type View = Either<A::View, B::View>;
fn as_id(&self) -> RouteMatchId {
match self {
Either::Left(_) => RouteMatchId(0),
Either::Right(_) => RouteMatchId(1),
}
}
fn as_matched(&self) -> &str {
match self {
Either::Left(i) => i.as_matched(),
Either::Right(i) => i.as_matched(),
}
}
fn to_params(&self) -> Self::Params { fn to_params(&self) -> Self::Params {
match self { match self {
Either::Left(i) => Either::Left(i.to_params().into_iter()), Either::Left(i) => Either::Left(i.to_params().into_iter()),
@ -113,14 +149,17 @@ where
type Data = (A::Data, B::Data); type Data = (A::Data, B::Data);
type Match = Either<A::Match, B::Match>; type Match = Either<A::Match, B::Match>;
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) { fn match_nested(
&'a self,
path: &'a str,
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let (A, B) = &self; let (A, B) = &self;
if let (Some(matched), remaining) = A.match_nested(path) { if let (Some((id, matched)), remaining) = A.match_nested(path) {
return (Some(Either::Left(matched)), remaining); return (Some((id, Either::Left(matched))), remaining);
} }
if let (Some(matched), remaining) = B.match_nested(path) { if let (Some((id, matched)), remaining) = B.match_nested(path) {
return (Some(Either::Right(matched)), remaining); return (Some((id, Either::Right(matched))), remaining);
} }
(None, path) (None, path)
} }
@ -152,7 +191,7 @@ macro_rules! chain_generated {
} }
macro_rules! tuples { macro_rules! tuples {
($either:ident => $($ty:ident),*) => { ($either:ident => $($ty:ident = $count:expr),*) => {
impl<'a, $($ty,)*> MatchInterface<'a> for $either <$($ty,)*> impl<'a, $($ty,)*> MatchInterface<'a> for $either <$($ty,)*>
where where
$($ty: MatchInterface<'a>),*, $($ty: MatchInterface<'a>),*,
@ -165,6 +204,18 @@ macro_rules! tuples {
type Child = $either<$($ty::Child,)*>; type Child = $either<$($ty::Child,)*>;
type View = $either<$($ty::View,)*>; type View = $either<$($ty::View,)*>;
fn as_id(&self) -> RouteMatchId {
match self {
$($either::$ty(_) => RouteMatchId($count),)*
}
}
fn as_matched(&self) -> &str {
match self {
$($either::$ty(i) => i.as_matched(),)*
}
}
fn to_params(&self) -> Self::Params { fn to_params(&self) -> Self::Params {
match self { match self {
$($either::$ty(i) => $either::$ty(i.to_params().into_iter()),)* $($either::$ty(i) => $either::$ty(i.to_params().into_iter()),)*
@ -192,12 +243,15 @@ macro_rules! tuples {
type Data = ($($ty::Data,)*); type Data = ($($ty::Data,)*);
type Match = $either<$($ty::Match,)*>; type Match = $either<$($ty::Match,)*>;
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) { fn match_nested(&'a self, path: &'a str) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let ($($ty,)*) = &self; let ($($ty,)*) = &self;
$(if let (Some(matched), remaining) = $ty.match_nested(path) { let mut id = 0;
return (Some($either::$ty(matched)), remaining); $(if let (Some((_, matched)), remaining) = $ty.match_nested(path) {
return (Some((RouteMatchId(id), $either::$ty(matched))), remaining);
} else {
id += 1;
})* })*
(None, path) (None, path)
} }
@ -215,17 +269,17 @@ macro_rules! tuples {
} }
} }
tuples!(EitherOf3 => A, B, C); tuples!(EitherOf3 => A = 0, B = 1, C = 2);
tuples!(EitherOf4 => A, B, C, D); tuples!(EitherOf4 => A = 0, B = 1, C = 2, D = 3);
tuples!(EitherOf5 => A, B, C, D, E); tuples!(EitherOf5 => A = 0, B = 1, C = 2, D = 3, E = 4);
tuples!(EitherOf6 => A, B, C, D, E, F); tuples!(EitherOf6 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5);
tuples!(EitherOf7 => A, B, C, D, E, F, G); tuples!(EitherOf7 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6);
tuples!(EitherOf8 => A, B, C, D, E, F, G, H); tuples!(EitherOf8 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7);
tuples!(EitherOf9 => A, B, C, D, E, F, G, H, I); tuples!(EitherOf9 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8);
tuples!(EitherOf10 => A, B, C, D, E, F, G, H, I, J); tuples!(EitherOf10 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9);
tuples!(EitherOf11 => A, B, C, D, E, F, G, H, I, J, K); tuples!(EitherOf11 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10);
tuples!(EitherOf12 => A, B, C, D, E, F, G, H, I, J, K, L); tuples!(EitherOf12 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11);
tuples!(EitherOf13 => A, B, C, D, E, F, G, H, I, J, K, L, M); tuples!(EitherOf13 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12);
tuples!(EitherOf14 => A, B, C, D, E, F, G, H, I, J, K, L, M, N); tuples!(EitherOf14 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12, N = 13);
tuples!(EitherOf15 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); tuples!(EitherOf15 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12, N = 13, O = 14);
tuples!(EitherOf16 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); tuples!(EitherOf16 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12, N = 13, O = 14, P = 15);

View file

@ -16,7 +16,7 @@ where
B: Mountable<Rndr>, B: Mountable<Rndr>,
Rndr: Renderer, Rndr: Renderer,
{ {
state: Either<A, B>, pub state: Either<A, B>,
marker: Rndr::Placeholder, marker: Rndr::Placeholder,
} }