Initial commit

This commit is contained in:
2022-07-22 22:38:58 +02:00
commit e0a428a8f9
6 changed files with 213 additions and 0 deletions

View 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"]

View 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)
}