diff --git a/.vscode/settings.json b/.vscode/settings.json index ab2b79d..df134c9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "rust-analyzer.cargo.features": ["sweet/bevy"], + "rust-analyzer.cargo.features": "all", // "rust-analyzer.cargo.target": "wasm32-unknown-unknown", "deno.enable": true, "search.exclude": { diff --git a/Cargo.lock b/Cargo.lock index 22bc3bf..b1a49e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,6 +101,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "any_spawner" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41058deaa38c9d9dd933d6d238d825227cffa668e2839b52879f6619c63eee3b" +dependencies = [ + "futures", + "thiserror 2.0.11", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -131,6 +141,17 @@ dependencies = [ "slab", ] +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-task" version = "4.7.1" @@ -489,7 +510,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn", ] @@ -766,6 +787,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -854,6 +886,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "extend" version = "1.2.0" @@ -990,9 +1043,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1005,9 +1058,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1015,15 +1068,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1032,9 +1085,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1051,9 +1104,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -1062,21 +1115,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1146,6 +1199,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "guardian" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493913a18c0d7bebb75127a26a432162c59edbe06f6cf712001e3e769345e8b5" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1483,6 +1542,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "or_poisoned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" + [[package]] name = "overload" version = "0.1.1" @@ -1586,6 +1651,29 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -1595,6 +1683,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1670,6 +1771,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reactive_graph" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bee22d7574c73fbfd47d828ee14dc67ca65606ade81de2f8d1691741072a93b" +dependencies = [ + "any_spawner", + "async-lock", + "futures", + "guardian", + "or_poisoned", + "pin-project-lite", + "rustc-hash 2.1.0", + "send_wrapper", + "slotmap", + "thiserror 2.0.11", + "web-sys", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1732,6 +1852,21 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "rstml" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51187e564f12336ef40cd04f6f4d805d6919188001dcf1e0a021898ea0fe28ce" +dependencies = [ + "derive-where", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", + "syn_derive", + "thiserror 1.0.63", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1744,6 +1879,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustix" version = "0.38.34" @@ -1772,6 +1913,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + [[package]] name = "serde" version = "1.0.204" @@ -1857,6 +2007,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1937,6 +2096,33 @@ dependencies = [ "syn", ] +[[package]] +name = "sweet_rsx" +version = "0.3.0-rc.1" +dependencies = [ + "sweet_rsx_macros", +] + +[[package]] +name = "sweet_rsx_macros" +version = "0.3.0-rc.1" +dependencies = [ + "anyhow", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "rstml", + "syn", +] + +[[package]] +name = "sweet_site" +version = "0.3.0-rc.1" +dependencies = [ + "reactive_graph", + "sweet_rsx", +] + [[package]] name = "syn" version = "2.0.96" @@ -1948,6 +2134,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -2429,6 +2627,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 4817335..a4d517a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,12 @@ [workspace] -resolver = "2" # Important! wgpu/Bevy needs this! -members = ["macros", "cli"] +resolver = "2" # Important! wgpu/Bevy needs this! +members = [ + "macros", + "cli", + "crates/sweet_rsx", + "crates/sweet_rsx/macros", + "crates/sweet_site", +] [workspace.package] version = "0.3.0-rc.1" @@ -15,6 +21,8 @@ repository = "https://github.com/mrchantey/sweet" [workspace.dependencies] sweet = { path = ".", version = "0.3.0-rc.1" } sweet_macros = { path = "./macros", version = "0.3.0-rc.1" } +sweet_rsx = { path = "crates/sweet_rsx", version = "0.3.0-rc.1" } +sweet_rsx_macros = { path = "crates/sweet_rsx/macros", version = "0.3.0-rc.1" } # forky = { version = "0.2.0-rc.2", path = "../forky/crates/forky" } # forky = { git = "https://github.com/mrchantey/forky" } forky = { path = "../forky" } diff --git a/crates/sweet_rsx/Cargo.toml b/crates/sweet_rsx/Cargo.toml new file mode 100644 index 0000000..1c0f8ef --- /dev/null +++ b/crates/sweet_rsx/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sweet_rsx" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +repository.workspace = true + + +[dependencies] +sweet_rsx_macros.workspace = true \ No newline at end of file diff --git a/crates/sweet_rsx/Cargo.toml/README.md b/crates/sweet_rsx/README.md similarity index 100% rename from crates/sweet_rsx/Cargo.toml/README.md rename to crates/sweet_rsx/README.md diff --git a/crates/sweet_rsx/examples/html_to_string.rs b/crates/sweet_rsx/examples/html_to_string.rs new file mode 100644 index 0000000..20f8c44 --- /dev/null +++ b/crates/sweet_rsx/examples/html_to_string.rs @@ -0,0 +1,39 @@ +#![allow(non_camel_case_types)] +pub use sweet_rsx::html; +pub use sweet_rsx::html_ide; +// Using this parser, one can write docs and link html tags to them. +// if this macro would be independent, it would be nicer to have docs in +// separate crate. +pub mod docs { + /// Element has open and close tags, content and attributes. + pub fn element() {} + /// Its a div! + pub struct div; + // pub fn div() {} +} + + +fn my_component() -> String { + html! {
this is a component
} +} + + +fn main() { + let component = my_component(); + + // {val} + let val = 2; + let html = html! { +
+ Hello, + the value is {val} +
+ }; + + + println!("html: {}", html); +} diff --git a/crates/sweet_rsx/examples/macro.rs b/crates/sweet_rsx/examples/macro.rs new file mode 100644 index 0000000..3a35f55 --- /dev/null +++ b/crates/sweet_rsx/examples/macro.rs @@ -0,0 +1,4 @@ +pub fn main() { + + println!("Hello, world!"); +} diff --git a/crates/sweet_rsx/macros/Cargo.toml b/crates/sweet_rsx/macros/Cargo.toml new file mode 100644 index 0000000..55d4ab6 --- /dev/null +++ b/crates/sweet_rsx/macros/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sweet_rsx_macros" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +repository.workspace = true + +[lib] +proc-macro = true + +[dependencies] +syn = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } +anyhow = { workspace = true } + +rstml = { version = "0.12", features = ["rawtext-stable-hack"] } +proc-macro2-diagnostics = "0.10" diff --git a/crates/sweet_rsx/macros/src/lib.rs b/crates/sweet_rsx/macros/src/lib.rs new file mode 100644 index 0000000..b56de16 --- /dev/null +++ b/crates/sweet_rsx/macros/src/lib.rs @@ -0,0 +1,43 @@ +use proc_macro::TokenStream; +use quote::quote; +use quote::ToTokens; +mod rstml_demo; + + +#[proc_macro] +pub fn rsx(_tokens: TokenStream) -> TokenStream { + quote! {}.to_token_stream().into() + // view_macro_impl(tokens, false) +} + + +/// Converts HTML to `String`. +/// +/// Values returned from braced blocks `{}` are expected to return something +/// that implements `Display`. +/// +/// See [rstml docs](https://docs.rs/rstml/) for supported tags and syntax. +/// +/// # Example +/// +/// ``` +/// use rstml_to_string_macro::html; +/// // using this macro, one should write docs module on top level of crate. +/// // Macro will link html tags to them. +/// pub mod docs { +/// /// Element has open and close tags, content and attributes. +/// pub fn element() {} +/// } +/// # fn main (){ +/// +/// let world = "planet"; +/// assert_eq!(html!(
"hello "{world}
), "
hello planet
"); +/// # } +/// ``` +#[proc_macro] +pub fn html(tokens: TokenStream) -> TokenStream { rstml_demo::html_inner(tokens, false) } + +/// Same as html but also emit IDE helper statements. +/// Open tests.rs in ide to see semantic highlight/goto def and docs. +#[proc_macro] +pub fn html_ide(tokens: TokenStream) -> TokenStream { rstml_demo::html_inner(tokens, true) } diff --git a/crates/sweet_rsx/macros/src/rstml_demo/html_to_string.rs b/crates/sweet_rsx/macros/src/rstml_demo/html_to_string.rs new file mode 100644 index 0000000..99ba2ba --- /dev/null +++ b/crates/sweet_rsx/macros/src/rstml_demo/html_to_string.rs @@ -0,0 +1,265 @@ +use proc_macro::TokenStream; +use quote::quote; +use quote::quote_spanned; +use quote::ToTokens; +use rstml::node::Node; +use rstml::node::NodeAttribute; +use rstml::node::NodeName; +use rstml::visitor::visit_attributes; +use rstml::visitor::visit_nodes; +use rstml::visitor::Visitor; +use rstml::Parser; +use rstml::ParserConfig; +use std::collections::HashSet; +use syn::spanned::Spanned; +// mod escape; +#[derive(Default)] +struct WalkNodesOutput { + static_format: String, + // Use proc_macro2::TokenStream instead of syn::Expr + // to provide more errors to the end user. + values: Vec, + // Additional diagnostic messages. + diagnostics: Vec, + // Collect elements to provide semantic highlight based on element tag. + // No differences between open tag and closed tag. + // Also multiple tags with same name can be present, + // because we need to mark each of them. + collected_elements: Vec, +} +struct WalkNodes<'a> { + empty_elements: &'a HashSet<&'a str>, + output: WalkNodesOutput, +} +impl<'a> WalkNodes<'a> { + fn child_output(&self) -> Self { + Self { + empty_elements: self.empty_elements, + output: WalkNodesOutput::default(), + } + } +} + +fn log_visit(prefix: &str, tokens: impl ToTokens) { + println!("🚀 {}: {}", prefix, tokens.into_token_stream().to_string()); +} + +impl WalkNodesOutput { + fn extend(&mut self, other: WalkNodesOutput) { + self.static_format.push_str(&other.static_format); + self.values.extend(other.values); + self.diagnostics.extend(other.diagnostics); + self.collected_elements.extend(other.collected_elements); + } +} +impl<'a> syn::visit_mut::VisitMut for WalkNodes<'a> {} + +impl<'a, C> Visitor for WalkNodes<'a> +where + C: rstml::node::CustomNode + 'static, +{ + fn visit_doctype( + &mut self, + doctype: &mut rstml::node::NodeDoctype, + ) -> bool { + log_visit("DOCTYPE", &doctype); + let value = &doctype.value.to_token_stream_string(); + self.output + .static_format + .push_str(&format!("", value)); + false + } + fn visit_text_node(&mut self, node: &mut rstml::node::NodeText) -> bool { + log_visit("TEXT", &node); + self.output.static_format.push_str(&node.value_string()); + false + } + fn visit_raw_node( + &mut self, + node: &mut rstml::node::RawText, + ) -> bool { + log_visit("RAW", &node); + self.output.static_format.push_str(&node.to_string_best()); + false + } + fn visit_fragment( + &mut self, + fragment: &mut rstml::node::NodeFragment, + ) -> bool { + log_visit("FRAGMENT", &fragment); + let visitor = self.child_output(); + let child_output = visit_nodes(&mut fragment.children, visitor); + self.output.extend(child_output.output); + false + } + + fn visit_comment( + &mut self, + comment: &mut rstml::node::NodeComment, + ) -> bool { + log_visit("COMMENT", &comment); + self.output + .static_format + .push_str(&format!("", comment.value.value())); + false + } + fn visit_block(&mut self, block: &mut rstml::node::NodeBlock) -> bool { + log_visit("BLOCK", &block); + self.output.static_format.push_str("{}"); + self.output.values.push(block.to_token_stream()); + false + } + fn visit_element( + &mut self, + element: &mut rstml::node::NodeElement, + ) -> bool { + log_visit("ELEMENT", &element); + let name = element.name().to_string(); + self.output.static_format.push_str(&format!("<{}", name)); + self.output + .collected_elements + .push(element.open_tag.name.clone()); + if let Some(e) = &element.close_tag { + self.output.collected_elements.push(e.name.clone()) + } + + let visitor = self.child_output(); + let attribute_visitor = + visit_attributes(element.attributes_mut(), visitor); + self.output.extend(attribute_visitor.output); + + self.output.static_format.push('>'); + + // Ignore childs of special Empty elements + if self + .empty_elements + .contains(element.open_tag.name.to_string().as_str()) + { + self.output + .static_format + .push_str(&format!("/", element.open_tag.name)); + if !element.children.is_empty() { + let warning = proc_macro2_diagnostics::Diagnostic::spanned( + element.open_tag.name.span(), + proc_macro2_diagnostics::Level::Warning, + "Element is processed as empty, and cannot have any child", + ); + self.output.diagnostics.push(warning.emit_as_expr_tokens()) + } + + return false; + } + // children + + let visitor = self.child_output(); + let child_output = visit_nodes(&mut element.children, visitor); + self.output.extend(child_output.output); + self.output.static_format.push_str(&format!("", name)); + false + } + fn visit_attribute(&mut self, attribute: &mut NodeAttribute) -> bool { + log_visit("ATTRIBUTE", &attribute); + // attributes + match attribute { + NodeAttribute::Block(block) => { + // log_visit("ATTRIBUTE - BLOCK", block.clone()); + // If the nodes parent is an attribute we prefix with whitespace + self.output.static_format.push(' '); + self.output.static_format.push_str("{}"); + self.output.values.push(block.to_token_stream()); + } + NodeAttribute::Attribute(attribute) => { + // log_visit("ATTRIBUTE - VANILLA", attribute.clone()); + self.output + .static_format + .push_str(&format!(" {}", attribute.key)); + if let Some(value) = attribute.value() { + self.output.static_format.push_str(r#"="event-handler-1""#); + // self.output.static_format.push_str(r#"="{}""#); + // self.output.values.push(value.to_token_stream()); + } + } + } + false + } +} +fn walk_nodes<'a>( + empty_elements: &'a HashSet<&'a str>, + nodes: &'a mut [Node], +) -> WalkNodesOutput { + let visitor = WalkNodes { + empty_elements, + output: WalkNodesOutput::default(), + }; + let mut nodes = nodes.to_vec(); + let output = visit_nodes(&mut nodes, visitor); + output.output +} + +pub fn html_inner(tokens: TokenStream, ide_helper: bool) -> TokenStream { + // https://developer.mozilla.org/en-US/docs/Glossary/Empty_element + let empty_elements: HashSet<_> = [ + "area", "base", "br", "col", "embed", "hr", "img", "input", "link", + "meta", "param", "source", "track", "wbr", + ] + .into_iter() + .collect(); + let config = ParserConfig::new() + .recover_block(true) + .always_self_closed_elements(empty_elements.clone()) + .raw_text_elements(["script", "style"].into_iter().collect()) + .macro_call_pattern(quote!(html! {%%})); + + let parser = Parser::new(config); + let (mut nodes, errors) = parser.parse_recoverable(tokens).split_vec(); + + let WalkNodesOutput { + static_format: html_string, + values, + collected_elements: elements, + diagnostics, + } = walk_nodes(&empty_elements, &mut nodes); + let docs = if ide_helper { + generate_tags_docs(&elements) + } else { + vec![] + }; + let errors = errors + .into_iter() + .map(|e| e.emit_as_expr_tokens()) + .chain(diagnostics); + quote! { + { + // Make sure that "compile_error!(..);" can be used in this context. + #(#errors;)* + // Make sure that "enum x{};" and "let _x = crate::element;" can be used in this context + #(#docs;)* + format!(#html_string, #(#values),*) + } + } + .into() +} + +fn generate_tags_docs(elements: &[NodeName]) -> Vec { + // Mark some of elements as type, + // and other as elements as fn in crate::docs, + // to give an example how to link tag with docs. + let elements_as_type: HashSet<&'static str> = + vec!["html", "head", "meta", "link", "body"] + .into_iter() + .collect(); + + elements + .into_iter() + .map(|e| { + if elements_as_type.contains(&*e.to_string()) { + let element = quote_spanned!(e.span() => enum); + quote!({#element X{}}) + } else { + // let _ = crate::docs::element; + let element = quote_spanned!(e.span() => element); + quote!(let _ = crate::docs::#element) + } + }) + .collect() +} diff --git a/crates/sweet_rsx/macros/src/rstml_demo/mod.rs b/crates/sweet_rsx/macros/src/rstml_demo/mod.rs new file mode 100644 index 0000000..2688b1d --- /dev/null +++ b/crates/sweet_rsx/macros/src/rstml_demo/mod.rs @@ -0,0 +1,3 @@ +pub mod html_to_string; +#[allow(unused_imports)] +pub use self::html_to_string::*; diff --git a/crates/sweet_rsx/src/lib.rs b/crates/sweet_rsx/src/lib.rs index e69de29..b85fa34 100644 --- a/crates/sweet_rsx/src/lib.rs +++ b/crates/sweet_rsx/src/lib.rs @@ -0,0 +1,4 @@ +pub use sweet_rsx_macros::*; + + +pub mod prelude {} diff --git a/crates/sweet_site/Cargo.toml b/crates/sweet_site/Cargo.toml new file mode 100644 index 0000000..407606e --- /dev/null +++ b/crates/sweet_site/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sweet_site" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +repository.workspace = true + + +[dependencies] +sweet_rsx.workspace = true + +reactive_graph = "0.1" diff --git a/crates/sweet_site/examples/reactive_graph.rs b/crates/sweet_site/examples/reactive_graph.rs new file mode 100644 index 0000000..ac202b7 --- /dev/null +++ b/crates/sweet_site/examples/reactive_graph.rs @@ -0,0 +1,26 @@ +fn main() { + use reactive_graph::computed::ArcMemo; + use reactive_graph::effect::Effect; + use reactive_graph::prelude::Read; + use reactive_graph::prelude::Set; + use reactive_graph::signal::ArcRwSignal; + + let count = ArcRwSignal::new(1); + let double_count = ArcMemo::new({ + let count = count.clone(); + move |_| *count.read() * 2 + }); + let double_count2 = double_count.clone(); + + // the effect will run once initially + Effect::new(move |_| { + println!("double_count = {}", *double_count.read()); + }); + + // updating `count` will propagate changes to the dependencies, + // causing the effect to run again + + count.set(2); + assert_eq!(*double_count2.read(), 4); + // success but effect didnt run +} diff --git a/crates/sweet_site/out/pages/index.html b/crates/sweet_site/out/pages/index.html new file mode 100644 index 0000000..def985a --- /dev/null +++ b/crates/sweet_site/out/pages/index.html @@ -0,0 +1,15 @@ +

sweet as!

+ +
+ + The value is 200 for now + +
+ \ No newline at end of file diff --git a/crates/sweet_site/out/pages/index.rs b/crates/sweet_site/out/pages/index.rs new file mode 100644 index 0000000..139597f --- /dev/null +++ b/crates/sweet_site/out/pages/index.rs @@ -0,0 +1,2 @@ + + diff --git a/crates/sweet_site/src/components/Counter.rs b/crates/sweet_site/src/components/Counter.rs new file mode 100644 index 0000000..11a8065 --- /dev/null +++ b/crates/sweet_site/src/components/Counter.rs @@ -0,0 +1,18 @@ +use reactive_graph::signal::arc_signal; +use sweet_rsx::rsx; + +pub struct Props { + initial_value: i32, +} + +pub fn Counter(props: Props) { + let (value, set_value) = arc_signal(props.initial_value); + + rsx! { +
+ + The value is {value} for now + +
+ } +} \ No newline at end of file diff --git a/crates/sweet_site/src/components/HelloWorld.rs b/crates/sweet_site/src/components/HelloWorld.rs new file mode 100644 index 0000000..31d5a43 --- /dev/null +++ b/crates/sweet_site/src/components/HelloWorld.rs @@ -0,0 +1,17 @@ +use sweet_rsx::rsx; + + + + + + + + + + +#[allow(non_snake_case)] +pub fn HelloWorld() { + rsx! { + + } +} diff --git a/crates/sweet_site/src/components/mod.rs b/crates/sweet_site/src/components/mod.rs new file mode 100644 index 0000000..80fc892 --- /dev/null +++ b/crates/sweet_site/src/components/mod.rs @@ -0,0 +1,6 @@ +pub mod Counter; +#[allow(unused_imports)] +pub use self::Counter::*; +pub mod HelloWorld; +#[allow(unused_imports)] +pub use self::HelloWorld::*; diff --git a/crates/sweet_site/src/lib.rs b/crates/sweet_site/src/lib.rs new file mode 100644 index 0000000..274e67d --- /dev/null +++ b/crates/sweet_site/src/lib.rs @@ -0,0 +1,10 @@ +#![allow(non_snake_case, unused)] + +pub mod components; +pub mod pages; + + +pub mod prelude { + pub use crate::components::*; + pub use crate::pages::*; +} diff --git a/crates/sweet_site/src/pages/index.rs b/crates/sweet_site/src/pages/index.rs new file mode 100644 index 0000000..35d53ea --- /dev/null +++ b/crates/sweet_site/src/pages/index.rs @@ -0,0 +1,11 @@ +pub use crate::components::Counter; +pub use sweet_rsx::rsx; + +pub fn route() { + let initial_value = 200; + + rsx! { +

Counter

+ + } +} diff --git a/crates/sweet_site/src/pages/mod.rs b/crates/sweet_site/src/pages/mod.rs new file mode 100644 index 0000000..3868d5a --- /dev/null +++ b/crates/sweet_site/src/pages/mod.rs @@ -0,0 +1,3 @@ +pub mod index; +#[allow(unused_imports)] +pub use self::index::*; diff --git a/justfile b/justfile index 040c305..4b3802c 100644 --- a/justfile +++ b/justfile @@ -77,10 +77,7 @@ test-runner test-binary *args: deno --allow-read run.ts {{args}} watch *command: - forky watch \ - -w '**/*.rs' \ - -w '**/*.ts' \ - -i '{.git,target,html}/**' \ - -i '**/mod.rs' \ - -i '**/*_g.rs' \ - -- {{command}} \ No newline at end of file + forky watch --rusty -- {{command}} + +expand-rsx: + just watch cargo expand -p sweet_rsx --example html_to_string \ No newline at end of file