Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
5
Cargo.toml
Normal file
5
Cargo.toml
Normal file
@ -0,0 +1,5 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"seed_router",
|
||||
"seed_router_derive",
|
||||
]
|
||||
10
seed_router/Cargo.toml
Normal file
10
seed_router/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "seed_router"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
seed = "*"
|
||||
|
||||
[dependencies.seed_router_derive]
|
||||
path = "../seed_router_derive"
|
||||
19
seed_router/src/lib.rs
Normal file
19
seed_router/src/lib.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use seed::prelude::{Node, Orders, Url};
|
||||
|
||||
pub use seed_router_derive::Router;
|
||||
|
||||
pub trait Page {
|
||||
type Msg: 'static;
|
||||
fn new(orders: &mut impl Orders<Self::Msg>) -> Self;
|
||||
fn update(&mut self, msg: Self::Msg, orders: &mut impl Orders<Self::Msg>);
|
||||
fn view(&self) -> Node<Self::Msg>;
|
||||
}
|
||||
|
||||
pub trait Router: Sized {
|
||||
type Msg: 'static;
|
||||
fn from_url(url: Url, orders: &mut impl Orders<Self::Msg>) -> Option<Self>;
|
||||
fn update(&mut self, msg: Self::Msg, orders: &mut impl Orders<Self::Msg>);
|
||||
fn view(&self) -> Node<Self::Msg>;
|
||||
}
|
||||
|
||||
pub const fn assert_impl_page(_: &impl Page) {}
|
||||
19
seed_router_derive/Cargo.toml
Normal file
19
seed_router_derive/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "seed_router_derive"
|
||||
version = "0.1.0"
|
||||
authors = ["Joakim Hulthe <joakim@hulthe.net>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc_macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
regex = "1"
|
||||
serde = "1"
|
||||
seed = "*"
|
||||
|
||||
[dependencies.syn]
|
||||
version = "1"
|
||||
features = ["derive", "parsing", "extra-traits"]
|
||||
158
seed_router_derive/src/lib.rs
Normal file
158
seed_router_derive/src/lib.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{
|
||||
parse_macro_input, spanned::Spanned, Data, DeriveInput, Error, Ident, Lit, Meta, NestedMeta,
|
||||
};
|
||||
|
||||
#[proc_macro_derive(Router, attributes(page))]
|
||||
pub fn derive_router(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match inner_derive_router(input) {
|
||||
Ok(tokens) => tokens.into(),
|
||||
Err(e) => e.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_derive_router(input: DeriveInput) -> syn::Result<TokenStream> {
|
||||
let ident = input.ident;
|
||||
let data = match input.data {
|
||||
Data::Enum(data) => data,
|
||||
Data::Struct(_) => panic!("Struct not supported"),
|
||||
Data::Union(_) => panic!("Union not supported"),
|
||||
};
|
||||
|
||||
let mut pages = vec![];
|
||||
|
||||
// TODO
|
||||
let msg_type = Ident::new("PageMsg", Span::call_site());
|
||||
|
||||
// parse enum
|
||||
let page_attribute = Ident::new("page", Span::call_site());
|
||||
for punct in data.variants.into_iter() {
|
||||
assert!(
|
||||
punct.discriminant.is_none(),
|
||||
"Enum discriminant not supported"
|
||||
);
|
||||
|
||||
let ident = punct.ident;
|
||||
let fields = punct.fields;
|
||||
let attributes = punct.attrs;
|
||||
let page_attribute = attributes
|
||||
.iter()
|
||||
.map(|attribute| attribute.parse_meta().expect("expected a string literatl"))
|
||||
.filter_map(|meta| match meta {
|
||||
Meta::Path(_) | Meta::NameValue(_) => None,
|
||||
Meta::List(meta_list) => Some(meta_list),
|
||||
})
|
||||
.find(|meta_list| meta_list.path.is_ident(&page_attribute))
|
||||
.ok_or_else(|| Error::new(ident.span(), "Missing \"page\" attribute"))?;
|
||||
|
||||
let mut page_attribute_values = page_attribute.nested.iter();
|
||||
let page_location = match page_attribute_values.next() {
|
||||
Some(NestedMeta::Lit(Lit::Str(literal))) => literal.value(),
|
||||
Some(NestedMeta::Lit(literal)) => {
|
||||
return Err(Error::new(literal.span(), "Expected string literal"))
|
||||
}
|
||||
Some(NestedMeta::Meta(meta)) => {
|
||||
return Err(Error::new(meta.span(), "Expected string literal"))
|
||||
}
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
page_attribute.span(),
|
||||
"Missing attribute value: location",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let msg_type = match page_attribute_values.next() {
|
||||
Some(NestedMeta::Meta(Meta::Path(path))) => path
|
||||
.get_ident()
|
||||
.cloned()
|
||||
.ok_or_else(|| Error::new(path.span(), "Expected Identifier"))?,
|
||||
Some(NestedMeta::Meta(meta)) => {
|
||||
return Err(Error::new(meta.span(), "Expected identifier"))
|
||||
}
|
||||
Some(NestedMeta::Lit(literal)) => {
|
||||
return Err(Error::new(literal.span(), "Expected identifier"))
|
||||
}
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
page_attribute.span(),
|
||||
"Missing attribute value: Message Type",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
pages.push((ident, page_location, msg_type));
|
||||
}
|
||||
|
||||
// generate implementation
|
||||
let from_url_match_arms: TokenStream = pages
|
||||
.iter()
|
||||
.map(|(variant, page_location, msg_variant)| {
|
||||
quote_spanned! { variant.span() =>
|
||||
[#page_location] => {
|
||||
let mut orders = orders.proxy(#msg_type::#msg_variant);
|
||||
Some(Self::#variant(::seed_router::Page::new(&mut orders)))
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let update_match_arms: TokenStream = pages
|
||||
.iter()
|
||||
.map(|(variant, _page_location, msg_variant)| {
|
||||
quote_spanned! { variant.span() =>
|
||||
Self::#variant(page) => {
|
||||
::seed_router::assert_impl_page(page);
|
||||
if let #msg_type::#msg_variant(msg) = msg {
|
||||
let mut orders = orders.proxy(#msg_type::#msg_variant);
|
||||
page.update(msg, &mut orders);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let view_match_arms: TokenStream = pages
|
||||
.iter()
|
||||
.map(|(variant, _page_location, msg_variant)| {
|
||||
quote_spanned! { variant.span() =>
|
||||
Self::#variant(page) => page.view().map_msg(#msg_type::#msg_variant),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let output = quote! {
|
||||
impl ::seed_router::Router for #ident {
|
||||
type Msg = #msg_type;
|
||||
|
||||
fn from_url(
|
||||
mut url: ::seed::prelude::Url,
|
||||
orders: &mut impl ::seed::prelude::Orders<Self::Msg>,
|
||||
) -> Option<Self> {
|
||||
use ::seed_router::Page as _;
|
||||
match url.remaining_path_parts().as_slice() {
|
||||
#from_url_match_arms
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, orders: &mut impl ::seed::prelude::Orders<Self::Msg>) {
|
||||
use ::seed_router::Page as _;
|
||||
match self {
|
||||
#update_match_arms
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> ::seed::prelude::Node<Self::Msg> {
|
||||
use ::seed_router::Page as _;
|
||||
match self {
|
||||
#view_match_arms
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
Reference in New Issue
Block a user