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