summaryrefslogtreecommitdiffstats
path: root/rocie-macros
diff options
context:
space:
mode:
Diffstat (limited to 'rocie-macros')
-rw-r--r--rocie-macros/Cargo.toml2
-rw-r--r--rocie-macros/src/form/generate.rs255
-rw-r--r--rocie-macros/src/form/mod.rs11
-rw-r--r--rocie-macros/src/form/parse.rs93
4 files changed, 277 insertions, 84 deletions
diff --git a/rocie-macros/Cargo.toml b/rocie-macros/Cargo.toml
index 01e9e5c..afbb942 100644
--- a/rocie-macros/Cargo.toml
+++ b/rocie-macros/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2024"
[dependencies]
proc-macro2 = "1.0.101"
quote = "1.0.41"
-syn = {version = "2.0.106", features = []}
+syn = { version = "2.0.106", features = ["full"] }
prettyplease = "0.2"
[lib]
diff --git a/rocie-macros/src/form/generate.rs b/rocie-macros/src/form/generate.rs
index 6673ba5..5642e6a 100644
--- a/rocie-macros/src/form/generate.rs
+++ b/rocie-macros/src/form/generate.rs
@@ -1,7 +1,7 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
-use syn::{Ident, parse_macro_input};
+use syn::{Ident, Type, parse_macro_input};
use crate::form::{ParsedChild, ParsedInput};
@@ -10,6 +10,8 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
let node_refs = input.children.iter().map(node_ref);
let signals = input.children.iter().map(signal);
+ let reactive_signals = input.children.iter().map(reactive_signal);
+ let auto_complete_signals = input.children.iter().map(auto_complete_signal);
let type_verifications = input.children.iter().map(type_verification);
@@ -20,6 +22,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
let bounds = input.on_submit.inputs;
let on_submit_block = input.on_submit.block;
+ let on_submit_move = if input.on_submit.should_use_move {
+ quote! {move}
+ } else {
+ quote! {}
+ };
let input_htmls = input.children.iter().map(input_html);
@@ -50,11 +57,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
#(
#node_refs
- )*
- #(
+
#signals
- )*
- #(
+ #reactive_signals
+ #auto_complete_signals
+
#type_verifications
)*
@@ -68,7 +75,7 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
#fetch_values
)*
- let real_on_submit = |Inputs {#(#bounds),*}| #on_submit_block;
+ let real_on_submit = #on_submit_move |Inputs {#(#bounds),*}| #on_submit_block;
real_on_submit(
#output_struct_construction
)
@@ -76,16 +83,17 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
view! {
- <form class="flex flex-col contents-start m-2 g-2" on:submit=on_submit>
+ <form class="relative flex flex-col contents-start m-2 g-2" on:submit=on_submit>
#(
#input_htmls
)*
- <div class="static">
+ <div class="sticky bottom-2 left-0 flex flex-row contents-start">
+ <div class="w-5/6" />
<input
type="submit"
value="Submit"
- class="absolute bottom-0 right-0 h-20 w-20 rounded-lg bg-green-300 m-2"
+ class="pr-2 pl-2 rounded-lg bg-green-300 m-2"
/>
</div>
</form>
@@ -112,15 +120,20 @@ fn node_ref_name(name: &Ident) -> Ident {
}
fn node_ref(child: &ParsedChild) -> TokenStream2 {
- let (name, ty) = match child {
- ParsedChild::Input { name, .. } => (name, quote! {Input}),
- ParsedChild::Select { name, .. } => (name, quote! {Select}),
- };
+ if let ParsedChild::Show { children, .. } = child {
+ children.iter().map(node_ref).collect()
+ } else {
+ let (name, ty) = match child {
+ ParsedChild::Input { name, .. } => (name, quote! {Input}),
+ ParsedChild::Select { name, .. } => (name, quote! {Select}),
+ ParsedChild::Show { .. } => unreachable!("Filtered out before"),
+ };
- let node_ref_name = node_ref_name(name);
+ let node_ref_name = node_ref_name(name);
- quote! {
- let #node_ref_name: NodeRef<#ty> = NodeRef::new();
+ quote! {
+ let #node_ref_name: NodeRef<#ty> = NodeRef::new();
+ }
}
}
@@ -137,42 +150,152 @@ fn signal(child: &ParsedChild) -> TokenStream2 {
}
}
ParsedChild::Select { .. } => quote! {},
+ ParsedChild::Show { children, .. } => children.iter().map(signal).collect(),
}
}
-fn type_verification(child: &ParsedChild) -> TokenStream2 {
+
+fn signal_auto_complete_names(name: &Ident) -> (Ident, Ident) {
+ (
+ format_ident!("{name}_auto_complete_get"),
+ format_ident!("{name}_auto_complete_set"),
+ )
+}
+fn auto_complete_signal(child: &ParsedChild) -> TokenStream2 {
match child {
- ParsedChild::Input { .. } => quote! {},
- ParsedChild::Select {
- rust_type, options, ..
+ ParsedChild::Input {
+ name,
+ reactive,
+ auto_complete,
+ ..
} => {
- let names: Vec<_> = options
- .0
- .iter()
- .enumerate()
- .map(|(index, _)| format_ident!("name_{index}"))
- .collect();
- let values: Vec<_> = options.0.iter().map(|option| option.1.clone()).collect();
+ if let Some(auto_complete) = auto_complete {
+ let (signal_auto_complete_get, _) = signal_auto_complete_names(name);
+
+ let reactive = reactive
+ .as_ref()
+ .expect("Must be some, if auto_complete is some");
+ quote! {
+ let #signal_auto_complete_get =
+ leptos::prelude::LocalResource::new(
+ move || #auto_complete(#reactive())
+ );
+ }
+ } else {
+ quote! {}
+ }
+ }
+ ParsedChild::Select { .. } => quote! {},
+ ParsedChild::Show { children, .. } => children.iter().map(auto_complete_signal).collect(),
+ }
+}
+
+fn signal_reactive_base_names(name: &Ident) -> (Ident, Ident) {
+ (
+ format_ident!("{name}_reactive_base_get"),
+ format_ident!("{name}_reactive_base_set"),
+ )
+}
+fn reactive_signal(child: &ParsedChild) -> TokenStream2 {
+ match child {
+ ParsedChild::Input {
+ name,
+ reactive,
+ rust_type,
+ ..
+ } => {
+ if let Some(reactive) = reactive {
+ let (signal_reactive_base_get, signal_reactive_base_set) =
+ signal_reactive_base_names(name);
+
+ quote! {
+ let (#signal_reactive_base_get, #signal_reactive_base_set) = signal(None);
+ #reactive = move || {
+ {
+ let output: Option<String> = #signal_reactive_base_get.get();
+
+ if let Some(output) = output {
+ let fin: Result<#rust_type, leptos::error::Error> = output
+ .parse()
+ .map_err(Into::<leptos::error::Error>::into);
+
+ match fin {
+ Ok(ok) => {
+ Some(ok)
+ },
+ Err(err) => {
+ // Reset the signal
+ None
+ }
+ }
+ } else {
+ None
+ }
+ }
+ };
+ }
+ } else {
+ quote! {}
+ }
+ }
+ ParsedChild::Select { .. } => quote! {},
+ ParsedChild::Show { children, .. } => children.iter().map(reactive_signal).collect(),
+ }
+}
+
+fn type_verification(child: &ParsedChild) -> TokenStream2 {
+ match child {
+ ParsedChild::Input { .. } => {
+ quote! {}
+ }
+ ParsedChild::Select { name, options, .. } => {
+ let name = format_ident!("_select_val_{name}");
quote! {
{
- #(
- let #names: #rust_type = #values;
- )*
+ let #name:
+ leptos::prelude::LocalResource<
+ Result<
+ // TODO: Use #rust_type instead of the second String <2025-10-21>
+ Vec<(String, String)>,
+ leptos::error::Error
+ >
+ > = #options;
}
}
}
+ ParsedChild::Show { when: _, children } => {
+ let base = children
+ .iter()
+ .map(type_verification)
+ .collect::<TokenStream2>();
+
+ quote! {
+ #base
+ }
+ }
+ }
+}
+
+fn get_names(input: &ParsedChild) -> Vec<&Ident> {
+ match input {
+ ParsedChild::Input { name, .. } => vec![name],
+ ParsedChild::Select { name, .. } => vec![name],
+ ParsedChild::Show { children, .. } => children.iter().flat_map(get_names).collect(),
}
}
fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 {
- let names = children.iter().map(|child| match child {
- ParsedChild::Input { name, .. } => name,
- ParsedChild::Select { name, .. } => name,
- });
- let rust_types = children.iter().map(|child| match child {
- ParsedChild::Input { rust_type, .. } => rust_type,
- ParsedChild::Select { rust_type, .. } => rust_type,
- });
+ fn get_rust_types(input: &ParsedChild) -> Vec<&Type> {
+ match input {
+ ParsedChild::Input { rust_type, .. } => vec![rust_type],
+ ParsedChild::Select { rust_type, .. } => vec![rust_type],
+ ParsedChild::Show { children, .. } => {
+ children.iter().flat_map(get_rust_types).collect()
+ }
+ }
+ }
+ let names = children.iter().flat_map(get_names);
+ let rust_types = children.iter().flat_map(get_rust_types);
quote! {
struct Inputs {
@@ -183,10 +306,7 @@ fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 {
}
}
fn output_struct_construction(children: &[ParsedChild]) -> TokenStream2 {
- let names = children.iter().map(|child| match child {
- ParsedChild::Input { name, .. } => name,
- ParsedChild::Select { name, .. } => name,
- });
+ let names = children.iter().flat_map(get_names);
quote! {
Inputs {
@@ -270,6 +390,9 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 {
};
}
}
+ ParsedChild::Show { children, .. } => {
+ children.iter().map(fetch_value).collect::<TokenStream2>()
+ }
}
}
@@ -280,13 +403,39 @@ fn input_html(child: &ParsedChild) -> TokenStream2 {
label,
rust_type,
html_type,
+ reactive,
+ auto_complete,
..
} => {
let node_ref_name = node_ref_name(name);
let (signal_name_get, _) = signal_names(name);
+ let reactive = {
+ if reactive.is_some() {
+ let (_, signal_reactive_set) = signal_reactive_base_names(name);
+
+ quote! {
+ reactive=Some(#signal_reactive_set)
+ }
+ } else {
+ quote! {}
+ }
+ };
+
+ let auto_complete = {
+ if auto_complete.is_some() {
+ let (signal_auto_complete_get, _) = signal_auto_complete_names(name);
+
+ quote! {
+ auto_complete=Some(#signal_auto_complete_get)
+ }
+ } else {
+ quote! {}
+ }
+ };
quote! {
- <InputPlaceholder input_type=#html_type label=#label node_ref=#node_ref_name />
+ <InputPlaceholder #reactive #auto_complete input_type=#html_type label=#label node_ref=#node_ref_name />
+
<Show
when=move || #signal_name_get.get().is_some()
fallback=|| ()
@@ -308,14 +457,22 @@ fn input_html(child: &ParsedChild) -> TokenStream2 {
..
} => {
let node_ref_name = node_ref_name(name);
- let options = options.0.iter().map(|(lit, expr)| {
- quote! {
- (#lit, #expr.to_string())
- }
- });
quote! {
- <SelectPlaceholder label=#label node_ref=#node_ref_name options=vec![#(#options),*] />
+ <SelectPlaceholder label=#label node_ref=#node_ref_name options=#options />
+ }
+ }
+ ParsedChild::Show { when, children } => {
+ let rendered_children = children.iter().map(input_html).collect::<Vec<_>>();
+
+ quote! {
+ <Show
+ when=#when
+ >
+ #(
+ #rendered_children
+ )*
+ </Show>
}
}
}
diff --git a/rocie-macros/src/form/mod.rs b/rocie-macros/src/form/mod.rs
index 2719826..7abd8f6 100644
--- a/rocie-macros/src/form/mod.rs
+++ b/rocie-macros/src/form/mod.rs
@@ -1,7 +1,7 @@
use syn::{Expr, Ident, LitStr, Type};
-mod parse;
mod generate;
+mod parse;
pub use generate::form_impl;
@@ -9,6 +9,7 @@ pub use generate::form_impl;
pub struct ParsedOnSubmit {
inputs: Vec<Ident>,
block: Expr,
+ pub(crate) should_use_move: bool,
}
#[derive(Debug)]
@@ -18,19 +19,19 @@ pub enum ParsedChild {
rust_type: Type,
html_type: LitStr,
label: LitStr,
+ reactive: Option<Ident>,
+ auto_complete: Option<Ident>,
},
Select {
name: Ident,
label: LitStr,
rust_type: Type,
- options: SelectOptions,
+ options: Expr, // Vec<(String, Uuid)>
},
+ Show { when: Expr, children: Vec<ParsedChild> },
}
#[derive(Debug)]
-pub struct SelectOptions(Vec<(LitStr, Expr)>);
-
-#[derive(Debug)]
pub struct ParsedInput {
on_submit: ParsedOnSubmit,
children: Vec<ParsedChild>,
diff --git a/rocie-macros/src/form/parse.rs b/rocie-macros/src/form/parse.rs
index ef2087b..b0ca58c 100644
--- a/rocie-macros/src/form/parse.rs
+++ b/rocie-macros/src/form/parse.rs
@@ -1,7 +1,10 @@
use quote::format_ident;
-use syn::{bracketed, parenthesized, parse::{Parse, ParseStream}, Expr, Ident, LitStr, Token, Type};
+use syn::{
+ Expr, Ident, LitStr, Token, Type,
+ parse::{Parse, ParseStream},
+};
-use crate::form::{ParsedChild, ParsedInput, ParsedOnSubmit, SelectOptions};
+use crate::form::{ParsedChild, ParsedInput, ParsedOnSubmit};
macro_rules! parse_key_value {
($input:expr, $name:ident as $ty:ty) => {{
@@ -19,27 +22,13 @@ macro_rules! parse_key_value {
value
}};
-}
-
-impl Parse for SelectOptions {
- fn parse(input: ParseStream) -> syn::Result<Self> {
- let content;
- bracketed!(content in input);
- let inner = content.parse_terminated(
- |arg| {
- let paren;
- parenthesized!(paren in arg);
- let lit = paren.parse::<LitStr>()?;
- paren.parse::<Token![,]>()?;
- let expr = paren.parse::<Expr>()?;
-
- Ok((lit, expr))
- },
- Token![,],
- )?;
-
- Ok(Self(inner.into_iter().collect()))
- }
+ (@option $input:expr, $name:ident as $ty:ty) => {{
+ if $input.peek(Ident) {
+ Some(parse_key_value!($input, $name as $ty))
+ } else {
+ None
+ }
+ }};
}
impl Parse for ParsedInput {
@@ -78,19 +67,33 @@ impl Parse for ParsedChild {
let rust_type = parse_key_value!(input, rust_type as Type);
let html_type = parse_key_value!(input, html_type as LitStr);
let label = parse_key_value!(input, label as LitStr);
+ let reactive = parse_key_value!(@option input, reactive as Ident);
+ let auto_complete = parse_key_value!(@option input, auto_complete as Ident);
+
+ if auto_complete.is_some() && reactive.is_none() {
+ panic!("Cannot provide an autocomplet, without registering an reactive signal")
+ }
+
+ input.parse::<Token![/]>()?;
+ input.parse::<Token![>]>()?;
ParsedChild::Input {
name,
rust_type,
html_type,
label,
+ reactive,
+ auto_complete,
}
}
variant if variant == format_ident!("Select") => {
let name = parse_key_value!(input, name as Ident);
let rust_type = parse_key_value!(input, rust_type as Type);
let label = parse_key_value!(input, label as LitStr);
- let options = parse_key_value!(input, options as SelectOptions);
+ let options = parse_key_value!(input, options as Expr);
+
+ input.parse::<Token![/]>()?;
+ input.parse::<Token![>]>()?;
ParsedChild::Select {
name,
@@ -99,11 +102,36 @@ impl Parse for ParsedChild {
rust_type,
}
}
- _ => panic!("Unkown form child variant: {variant}"),
- };
+ variant if variant == format_ident!("Show") => {
+ let when = parse_key_value!(input, when as Expr);
+
+ input.parse::<Token![>]>()?;
+
+ let children = {
+ let mut children = vec![];
+
+ while !(input.peek(Token![<]) && input.peek2(Token![/])) {
+ children.push(input.parse::<ParsedChild>()?);
+ }
+
+ {
+ input.parse::<Token![<]>()?;
+ input.parse::<Token![/]>()?;
+ let show_ident = input.parse::<Ident>()?;
+
+ if show_ident != format_ident!("Show") {
+ panic!("Expected key name to be 'Show', but found: '{show_ident}'",);
+ }
+ input.parse::<Token![>]>()?;
- input.parse::<Token![/]>()?;
- input.parse::<Token![>]>()?;
+ children
+ }
+ };
+
+ ParsedChild::Show { when, children }
+ }
+ _ => panic!("Unknown form child variant: {variant}"),
+ };
Ok(output)
}
@@ -113,6 +141,13 @@ impl Parse for ParsedOnSubmit {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut inputs = Vec::new();
+ let should_use_move = if input.peek(Token![move]) {
+ input.parse::<Token![move]>()?;
+ true
+ } else {
+ false
+ };
+
input.parse::<Token![|]>()?;
while !input.peek(Token![|]) {
inputs.push(input.parse::<Ident>()?);
@@ -127,6 +162,6 @@ impl Parse for ParsedOnSubmit {
input.parse::<Token![;]>()?;
- Ok(Self { inputs, block })
+ Ok(Self { inputs, block, should_use_move })
}
}