WIP: different macro API
This commit is contained in:
1 parent
74ace3f978
commit
87794b5039
2 files changed
+288
-1
No files matched your search
@@ -13,13 +13,14 @@ use proc_macro2::TokenStream as TokenStream2;
|
|||||||
use crate::{layout::LayoutId, route_tree::RouteTree};
|
use crate::{layout::LayoutId, route_tree::RouteTree};
|
||||||
|
|
||||||
mod layout;
|
mod layout;
|
||||||
|
mod macro2;
|
||||||
mod nest;
|
mod nest;
|
||||||
mod query;
|
mod query;
|
||||||
mod route;
|
mod route;
|
||||||
mod route_tree;
|
mod route_tree;
|
||||||
mod segment;
|
mod segment;
|
||||||
|
|
||||||
#[proc_macro_derive(Routable, attributes(route, nest, end_nest, layout, end_layout))]
|
#[proc_macro_derive(Routable)]
|
||||||
pub fn routable(input: TokenStream) -> TokenStream {
|
pub fn routable(input: TokenStream) -> TokenStream {
|
||||||
let routes_enum = parse_macro_input!(input as syn::ItemEnum);
|
let routes_enum = parse_macro_input!(input as syn::ItemEnum);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,286 @@
|
|||||||
|
use syn::{braced, parenthesized, parse::Parse, Expr, Ident, LitStr, Path, Token};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses() {
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
let tokens = quote! {
|
||||||
|
// The name of the enum
|
||||||
|
Route,
|
||||||
|
// All nests that have dynamic segments must have a name used to generate the enum
|
||||||
|
route(User, "user" / user_id: usize) {
|
||||||
|
route(Product, "product" / product_id: usize / dynamic: usize ) {
|
||||||
|
// Render creates a new route (that will be included in the enum) and is rendered with the given component
|
||||||
|
// The component uses the struct of the parent route as a prop (in this case, Product)
|
||||||
|
render(Other)
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can nest routes inside a layout to wrap them in a component that accepts the struct of the parent route as a prop (in this case, User)
|
||||||
|
layout(UserFrame) {
|
||||||
|
route(Route1Props, "hello_world" / dynamic: usize ) {
|
||||||
|
// (Accepts Route1Props as a prop)
|
||||||
|
render(Route1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can opt out of the layout by using !layout
|
||||||
|
!layout(UserFrame) {
|
||||||
|
route(Route2Props, "hello_world" / dynamic: usize ) {
|
||||||
|
// (Accepts Route2Props as a prop)
|
||||||
|
render(Route2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route(Route3Props, "hello_world" / dynamic: usize ) {
|
||||||
|
// (Accepts Route3Props as a prop)
|
||||||
|
render(Route3)
|
||||||
|
}
|
||||||
|
|
||||||
|
route(RedirectData, dynamic: usize / extra: String) {
|
||||||
|
// Redirects accept a function that receives the struct of the parent route and returns the new route
|
||||||
|
redirect(|data: RedirectData| todo!() )
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = syn::parse2::<RouteTree>(tokens).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RouteTree {
|
||||||
|
name: Ident,
|
||||||
|
roots: Vec<RouteSegment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for RouteTree {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let name = input.parse()?;
|
||||||
|
let _ = input.parse::<Token![,]>();
|
||||||
|
|
||||||
|
let mut roots = Vec::new();
|
||||||
|
while !input.is_empty() {
|
||||||
|
roots.push(input.parse()?);
|
||||||
|
}
|
||||||
|
Ok(Self { name, roots })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RouteSegment {
|
||||||
|
Route(Route),
|
||||||
|
Layout(Layout),
|
||||||
|
Render(Render),
|
||||||
|
Redirect(Redirect),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for RouteSegment {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let lookahead = input.lookahead1();
|
||||||
|
if lookahead.peek(syn::Token![!]) {
|
||||||
|
input.parse::<Token![!]>()?;
|
||||||
|
let ident: Ident = input.parse()?;
|
||||||
|
if ident == "layout" {
|
||||||
|
let mut layout: Layout = input.parse()?;
|
||||||
|
layout.opt_out = true;
|
||||||
|
Ok(RouteSegment::Layout(layout))
|
||||||
|
} else {
|
||||||
|
Err(lookahead.error())
|
||||||
|
}
|
||||||
|
} else if lookahead.peek(syn::Ident) {
|
||||||
|
let ident: Ident = input.parse()?;
|
||||||
|
if ident == "route" {
|
||||||
|
let route = input.parse()?;
|
||||||
|
Ok(RouteSegment::Route(route))
|
||||||
|
} else if ident == "layout" {
|
||||||
|
let layout = input.parse()?;
|
||||||
|
Ok(RouteSegment::Layout(layout))
|
||||||
|
} else if ident == "render" {
|
||||||
|
let render = input.parse()?;
|
||||||
|
Ok(RouteSegment::Render(render))
|
||||||
|
} else if ident == "redirect" {
|
||||||
|
let redirect = input.parse()?;
|
||||||
|
Ok(RouteSegment::Redirect(redirect))
|
||||||
|
} else {
|
||||||
|
Err(lookahead.error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(lookahead.error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Render {
|
||||||
|
component: Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Render {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let inner;
|
||||||
|
parenthesized!(inner in input);
|
||||||
|
let component = inner.parse()?;
|
||||||
|
|
||||||
|
Ok(Self { component })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Layout {
|
||||||
|
opt_out: bool,
|
||||||
|
component: Path,
|
||||||
|
children: Vec<RouteSegment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Layout {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let inner;
|
||||||
|
parenthesized!(inner in input);
|
||||||
|
let component = inner.parse()?;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
let mut children = Vec::new();
|
||||||
|
while !content.is_empty() {
|
||||||
|
children.push(content.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
opt_out: false,
|
||||||
|
component,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Route {
|
||||||
|
name: Ident,
|
||||||
|
path: RoutePath,
|
||||||
|
children: Vec<RouteSegment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Route {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let inner;
|
||||||
|
parenthesized!(inner in input);
|
||||||
|
let name = inner.parse()?;
|
||||||
|
inner.parse::<Token![,]>()?;
|
||||||
|
let path = inner.parse()?;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
let mut children = Vec::new();
|
||||||
|
while !content.is_empty() {
|
||||||
|
children.push(content.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoutePath {
|
||||||
|
segments: Vec<RoutePathSegment>,
|
||||||
|
query: Option<QuerySegment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for RoutePath {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
// parse all segments first
|
||||||
|
let mut segments = Vec::new();
|
||||||
|
// remove any leading slash
|
||||||
|
if input.peek(syn::Token![/]) {
|
||||||
|
input.parse::<syn::Token![/]>()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
let peak = input.lookahead1();
|
||||||
|
// check if the next segment is a query
|
||||||
|
if peak.peek(syn::Token![?]) {
|
||||||
|
break;
|
||||||
|
} else if peak.peek(syn::Token![/]) {
|
||||||
|
input.parse::<syn::Token![/]>()?;
|
||||||
|
} else if peak.peek(syn::Ident) || peak.peek(syn::Token![...]) || peak.peek(syn::LitStr)
|
||||||
|
{
|
||||||
|
// parse the segment
|
||||||
|
segments.push(input.parse()?);
|
||||||
|
} else {
|
||||||
|
return Err(peak.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// then parse the query
|
||||||
|
let query = if input.peek(syn::Token![?]) {
|
||||||
|
Some(input.parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(Self { segments, query })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoutePathSegment {
|
||||||
|
Static(String),
|
||||||
|
Dynamic(Ident, Path),
|
||||||
|
CatchAll(Ident, Path),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for RoutePathSegment {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let lookahead = input.lookahead1();
|
||||||
|
if lookahead.peek(Token![...]) {
|
||||||
|
input.parse::<Token![...]>()?;
|
||||||
|
let name: Ident = input.parse()?;
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
let type_: Path = input.parse()?;
|
||||||
|
|
||||||
|
// parse the /
|
||||||
|
let _ = input.parse::<Token![/]>();
|
||||||
|
|
||||||
|
Ok(RoutePathSegment::CatchAll(name, type_))
|
||||||
|
} else if lookahead.peek(LitStr) {
|
||||||
|
let lit: LitStr = input.parse()?;
|
||||||
|
|
||||||
|
// parse the /
|
||||||
|
let _ = input.parse::<Token![/]>();
|
||||||
|
|
||||||
|
Ok(RoutePathSegment::Static(lit.value()))
|
||||||
|
} else if lookahead.peek(Ident) {
|
||||||
|
let ident: Ident = input.parse()?;
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
let type_: Path = input.parse()?;
|
||||||
|
|
||||||
|
// parse the /
|
||||||
|
let _ = input.parse::<Token![/]>();
|
||||||
|
|
||||||
|
Ok(RoutePathSegment::Dynamic(ident, type_))
|
||||||
|
} else {
|
||||||
|
Err(lookahead.error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QuerySegment {
|
||||||
|
name: Ident,
|
||||||
|
type_: Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for QuerySegment {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
input.parse::<syn::Token![?]>()?;
|
||||||
|
let name = input.parse()?;
|
||||||
|
input.parse::<syn::Token![:]>()?;
|
||||||
|
let type_ = input.parse()?;
|
||||||
|
Ok(Self { name, type_ })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Redirect {
|
||||||
|
function: Expr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Redirect {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let inner;
|
||||||
|
parenthesized!(inner in input);
|
||||||
|
let function = inner.parse()?;
|
||||||
|
Ok(Self { function })
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user