Skip to content

Commit

Permalink
feat: HtmlPartial
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchantey committed Jan 15, 2025
1 parent 0bdc298 commit 7009651
Show file tree
Hide file tree
Showing 22 changed files with 677 additions and 622 deletions.
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"rust-analyzer.cargo.features": "all",
"rust-analyzer.cargo.target": "wasm32-unknown-unknown",
"rust-analyzer.cargo.features": [],
// "rust-analyzer.cargo.features": "all",
// "rust-analyzer.cargo.target": "wasm32-unknown-unknown",
"rust-analyzer.files.excludeDirs": [
// "crates/sweet_rsx/macros",
// "crates/sweet_test/macros",
Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions crates/sweet_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ keywords.workspace = true


[features]
serde = ["dep:serde"]
quote = ["dep:quote", "dep:proc-macro2"]
serde = ["dep:serde", "dep:bincode"]


[dependencies]
Expand All @@ -22,7 +23,9 @@ anyhow.workspace = true
flume.workspace = true
html-escape = "0.2.13"


bincode = { version = "1", optional = true }
quote = { workspace = true, optional = true }
proc-macro2 = { workspace = true, optional = true }
serde = { workspace = true, optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Expand Down
14 changes: 14 additions & 0 deletions crates/sweet_core/src/error/parse_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ pub enum ParseError {
Fs(FsError),
#[error("Hydration Error: {0}")]
Hydration(String),
#[error("Serde Error: {0}")]
Serde(String),
#[error("Parse Error: {0}")]
Other(String),
}
impl ParseError {
pub fn hydration(e: impl ToString) -> Self {
Self::Hydration(e.to_string())
}
}


#[cfg(not(target_arch = "wasm32"))]
impl From<FsError> for ParseError {
Expand All @@ -29,3 +37,9 @@ impl From<String> for ParseError {
impl From<&str> for ParseError {
fn from(e: &str) -> Self { Self::Other(e.to_string()) }
}


#[cfg(feature = "serde")]
impl From<bincode::Error> for ParseError {
fn from(e: bincode::Error) -> Self { Self::Serde(e.to_string()) }
}
116 changes: 110 additions & 6 deletions crates/sweet_core/src/rsx/html_partial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,140 @@ pub use serde::Deserialize;
#[cfg(feature = "serde")]
pub use serde::Serialize;

/// Very simple storage of html
#[derive(Debug)]
/// Very simple storage of html, without any
/// parsing or validation, for use with rstml output.
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HtmlPartial {
pub elements: Vec<Element>,
pub nodes: Vec<Node>,
}


impl HtmlPartial {
/// placeholder for rust blocks
pub const PLACEHOLDER: char = '§';
pub fn new() -> Self { Self::default() }

pub fn extend(&mut self, other: Self) {
let Self { nodes } = other;
self.nodes.extend(nodes);
}

pub fn to_string_placeholder(&self) -> String {
let mut out = String::new();
for node in &self.nodes {
out.push_str(&node.to_string_placeholder());
}
out
}
}


/// Minimum required info for our use case of html.
#[derive(Debug)]
/// Blocks are assumed to be `PartiaEq` because
/// they are defined as 'the next block in the vec' when reconciling.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Element {
/// ie `div, span, input`
pub tag: String,
/// ie `class="my-class"`
pub attributes: Vec<Attribute>,
/// ie `<div>childtext<childel/>{childblock}</div>`
pub children: Vec<Node>,
/// ie `<input/>`
pub self_closing: bool,
}

impl Element {
pub fn new(tag: String, self_closing: bool) -> Self {
Self {
tag,
self_closing,
attributes: Vec::new(),
children: Vec::new(),
}
}


/// Whether any children or attributes are blocks,
/// used to determine whether the node requires an id
pub fn contains_blocks(&self) -> bool {
self.children.iter().any(|c| matches!(c, Node::TextBlock))
|| self.attributes.iter().any(|a| {
matches!(a, Attribute::Block | Attribute::BlockValue { .. })
})
}


pub fn to_string_placeholder(&self) -> String {
let mut out = String::new();
let self_closing = if self.self_closing { "/" } else { "" };

out.push_str(&format!("<{}{}>", self.tag, self_closing));
for attribute in &self.attributes {
out.push(' ');
out.push_str(&attribute.to_string_placeholder());
}
for child in &self.children {
out.push_str(&child.to_string_placeholder());
}
if !self.self_closing {
out.push_str(&format!("</{}>", self.tag));
}
out
}
}

#[derive(Debug)]
/// a 'collapsed' rstml node
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Node {
Doctype,
Comment(String),
Element(Element),
/// may have been Text or RawText
Text(String),
/// contents is reconciled by renderer
TextBlock,
/// contents is reconciled by renderer
Component(Element),
}

impl Node {
pub fn to_string_placeholder(&self) -> String {
match self {
Node::Doctype => "<!DOCTYPE html>".to_string(),
Node::Comment(s) => format!("<!--{}-->", s),
Node::Element(e) => e.to_string_placeholder(),
Node::Text(s) => s.clone(),
Node::TextBlock => HtmlPartial::PLACEHOLDER.to_string(),
Node::Component(e) => e.to_string_placeholder(),
}
}
}


#[derive(Debug)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Attribute {
Key { key: String },
KeyValue { key: String, value: String },
BlockValue { key: String },
Block,
}

impl Attribute {
pub fn to_string_placeholder(&self) -> String {
match self {
Attribute::Key { key } => key.clone(),
Attribute::KeyValue { key, value } => {
format!("{}=\"{}\"", key, value)
}
Attribute::BlockValue { key } => {
format!("{}=\"{}\"", key, HtmlPartial::PLACEHOLDER)
}
Attribute::Block => HtmlPartial::PLACEHOLDER.to_string(),
}
}
}
64 changes: 64 additions & 0 deletions crates/sweet_core/src/rsx/html_partial_quote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#![cfg(feature = "quote")]
use super::*;
use proc_macro2::TokenStream;
use quote::quote;
use quote::ToTokens;

impl ToTokens for HtmlPartial {
fn to_tokens(&self, tokens: &mut TokenStream) {
let nodes = &self.nodes;
quote! {
HtmlPartial {
nodes: vec![#(#nodes),*],
}
}
.to_tokens(tokens);
}
}

impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream) {
let tag = &self.tag;
let attributes = &self.attributes;
let children = &self.children;
let self_closing = self.self_closing;
quote! {
Element {
tag: #tag.to_string(),
attributes: vec![#(#attributes),*],
children: vec![#(#children),*],
self_closing: #self_closing,
}
}
.to_tokens(tokens);
}
}

impl ToTokens for Node {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Node::Doctype => quote!(Node::Doctype),
Node::Comment(s) => quote!(Node::Comment(#s.to_string())),
Node::Element(e) => quote!(Node::Element(#e)),
Node::Text(s) => quote!(Node::Text(#s.to_string())),
Node::TextBlock => quote!(Node::TextBlock),
Node::Component(e) => quote!(Node::Component(#e)),
}
.to_tokens(tokens);
}
}

impl ToTokens for Attribute {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Attribute::Key { key } => quote!(Attribute::Key { key: #key.to_string() }),
Attribute::KeyValue { key, value } => {
quote!(Attribute::KeyValue { key: #key.to_string(), value: #value.to_string() })
}
Attribute::BlockValue { key } => {
quote!(Attribute::BlockValue { key: #key.to_string() })
}
Attribute::Block => quote!(Attribute::Block),
}.to_tokens(tokens);
}
}
3 changes: 3 additions & 0 deletions crates/sweet_core/src/rsx/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
pub mod html_partial;
#[allow(unused_imports)]
pub use self::html_partial::*;
pub mod html_partial_quote;
#[allow(unused_imports)]
pub use self::html_partial_quote::*;
pub mod hydrate;
#[allow(unused_imports)]
pub use self::hydrate::*;
Expand Down
Loading

0 comments on commit 7009651

Please sign in to comment.