WIP: different macro API

This commit is contained in:
Evan Almloff committed 2023-05-26 18:49:45 -05:00
1 parent 74ace3f978
commit 87794b5039
2 files changed
+288 -1

No files matched your search

+2 -1
View File
@@ -13,13 +13,14 @@ use proc_macro2::TokenStream as TokenStream2;
use crate::{layout::LayoutId, route_tree::RouteTree};
mod layout;
mod macro2;
mod nest;
mod query;
mod route;
mod route_tree;
mod segment;
#[proc_macro_derive(Routable, attributes(route, nest, end_nest, layout, end_layout))]
#[proc_macro_derive(Routable)]
pub fn routable(input: TokenStream) -> TokenStream {
let routes_enum = parse_macro_input!(input as syn::ItemEnum);
+286
View File
@@ -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 })
}
}