Skip to content

Instantly share code, notes, and snippets.

@joshuaclayton
Created September 24, 2020 17:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshuaclayton/a480fa2d931c558ca222248d5653c057 to your computer and use it in GitHub Desktop.
Save joshuaclayton/a480fa2d931c558ca222248d5653c057 to your computer and use it in GitHub Desktop.
diff --git a/src/attributes.rs b/src/attributes.rs
index e1641b2..a03d803 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -38,7 +38,9 @@ fn evaluate_attribute_value_components<'a>(
.iter()
.map(|v| match v {
AttributeValueComponent::RawValue(value) => value.to_string(),
- AttributeValueComponent::InterpolatedValue(values) => context.interpret(values),
+ AttributeValueComponent::InterpolatedValue(values) => {
+ context.interpret(values).unwrap_or("".to_string())
+ }
})
.collect()
}
diff --git a/src/context.rs b/src/context.rs
index e05703b..63becd7 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -36,8 +36,8 @@ impl Context {
Ok(Context { payload })
}
- pub fn interpret(&self, value: &[Selector]) -> String {
- self.at(value).map(value_to_string).unwrap_or("".into())
+ pub fn interpret(&self, value: &[Selector]) -> Option<String> {
+ self.at(value).map(value_to_string)
}
pub fn at(&self, value: &[Selector]) -> Option<&Value> {
diff --git a/src/node.rs b/src/node.rs
index 2f61c5f..d779a99 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -1,6 +1,6 @@
use super::{
context::{Context, Selector},
- Blocks, Fragments, Nodes, Tag,
+ Blocks, Builder, Fragments, Nodes, Tag,
};
use serde_json::Value;
use std::path::PathBuf;
@@ -27,67 +27,76 @@ pub enum Node<'a> {
},
}
+#[derive(Debug)]
+pub enum NodeError<'a> {
+ InvalidFragmentPath(PathBuf),
+ JSONValueMissingAtSelector(Vec<Selector<'a>>),
+ JSONValueNotArrayAtSelector(Vec<Selector<'a>>),
+}
+
impl<'a> Node<'a> {
pub fn to_html(
&self,
+ mut builder: Builder,
context: &Context,
fragments: &Fragments<'a>,
blocks: &Blocks<'a>,
styles: &'a Option<String>,
- ) -> String {
+ ) -> Builder {
match self {
- Node::Text(v) => v.to_string(),
- Node::InterpolatedText(v) => context.interpret(v),
+ Node::Text(v) => builder.append(v.to_string()),
+ Node::InterpolatedText(selectors) => match context.interpret(selectors) {
+ None => builder.warn(NodeError::JSONValueMissingAtSelector(selectors.to_vec())),
+ Some(value) => builder.append(value),
+ },
Node::BlockValue(name) => {
if let Some(boxed_nodes) = blocks.get(name) {
- (*boxed_nodes.to_html(context, fragments, blocks, styles)).to_string()
+ builder = boxed_nodes.to_html(builder, context, fragments, blocks, styles);
} else {
- "".into()
+ builder.warn("Unable to find block value")
}
}
- Node::Element { tag, children } => format!(
- "{}{}{}{}",
- tag.open_tag_html(context),
- children.to_html(context, fragments, blocks, styles),
- tag.additional_markup(styles),
- tag.close_tag_html()
- ),
+ Node::Element { tag, children } => {
+ builder.append(tag.open_tag_html(context));
+ builder = children.to_html(builder, context, fragments, blocks, styles);
+ builder.append(tag.additional_markup(styles));
+ builder.append(tag.close_tag_html());
+ }
Node::ForLoop {
local,
selectors,
children,
- } => {
- if let Some(Value::Array(loopable)) = context.at(selectors) {
- loopable
- .iter()
- .map(|looped_value| {
- children.to_html(
- &context.extend(local, looped_value),
- fragments,
- blocks,
- styles,
- )
- })
- .collect::<Vec<String>>()
- .join("")
- } else {
- "".into()
+ } => match context.at(selectors) {
+ None => builder.warn(NodeError::JSONValueMissingAtSelector(selectors.to_vec())),
+ Some(Value::Array(loopable)) => {
+ builder = loopable.iter().fold(builder, |acc, looped_value| {
+ children.to_html(
+ acc,
+ &context.extend(local, looped_value),
+ fragments,
+ blocks,
+ styles,
+ )
+ })
}
- }
+ Some(_) => builder.warn(NodeError::JSONValueNotArrayAtSelector(selectors.to_vec())),
+ },
Node::Fragment { path } => {
if let Some(nodes) = fragments.get(path) {
- nodes.to_html(context, fragments, blocks, styles)
+ builder = nodes.to_html(builder, context, fragments, blocks, styles)
} else {
- "".into()
+ builder.warn(NodeError::InvalidFragmentPath(path.to_path_buf()))
}
}
Node::Block { name, children } => {
if let Some(boxed_nodes) = blocks.get(name) {
- (*boxed_nodes.to_html(context, fragments, blocks, styles)).to_string()
+ builder = boxed_nodes.to_html(builder, context, fragments, blocks, styles)
} else {
- children.to_html(context, fragments, blocks, styles)
+ builder = children.to_html(builder, context, fragments, blocks, styles)
}
}
- }
+ };
+
+ builder
}
}
diff --git a/src/nodes.rs b/src/nodes.rs
index 33b3166..b107712 100644
--- a/src/nodes.rs
+++ b/src/nodes.rs
@@ -13,28 +13,30 @@ pub enum Nodes<'a> {
impl<'a> Nodes<'a> {
pub fn to_html(
&self,
+ mut builder: Builder,
context: &Context,
fragments: &Fragments<'a>,
blocks: &Blocks<'a>,
styles: &'a Option<String>,
- ) -> String {
+ ) -> Builder {
match self {
Nodes::Fragment { nodes } => {
- Self::nodes_to_html(nodes, context, fragments, blocks, styles)
+ builder = Self::nodes_to_html(builder, nodes, context, fragments, blocks, styles);
+ }
+ Nodes::Document { nodes } => {
+ builder.append("<!DOCTYPE html>".to_string());
+ builder = Self::nodes_to_html(builder, nodes, context, fragments, blocks, styles);
}
- Nodes::Document { nodes } => format!(
- "{}{}",
- "<!DOCTYPE html>",
- Self::nodes_to_html(nodes, context, fragments, blocks, styles)
- ),
Nodes::FragmentSubclass { layout, blocks } => {
if let Some(nodes) = fragments.get(layout) {
- nodes.to_html(context, fragments, blocks, styles)
+ builder = nodes.to_html(builder, context, fragments, blocks, styles)
} else {
- "".into()
+ builder.warn("Bad fragment")
}
}
}
+
+ builder
}
pub fn prepend(&mut self, node: Node<'a>) {
@@ -65,16 +67,39 @@ impl<'a> Nodes<'a> {
}
fn nodes_to_html(
+ builder: Builder,
nodes: &[Node],
context: &Context,
fragments: &Fragments<'a>,
blocks: &Blocks<'a>,
styles: &'a Option<String>,
- ) -> String {
- nodes
- .iter()
- .map(|n| n.to_html(context, fragments, blocks, styles))
- .collect::<Vec<String>>()
- .join("")
+ ) -> Builder {
+ nodes.iter().fold(builder, |acc, n| {
+ n.to_html(acc, context, fragments, blocks, styles)
+ })
+ }
+}
+
+pub struct Builder {
+ values: Vec<String>,
+}
+
+impl std::default::Default for Builder {
+ fn default() -> Self {
+ Builder { values: vec![] }
+ }
+}
+
+impl Builder {
+ pub fn append(&mut self, value: String) {
+ self.values.push(value)
+ }
+
+ pub fn warn<T: std::fmt::Debug>(&mut self, value: T) {
+ eprintln!("{:?}", value);
+ }
+
+ pub fn result(&self) -> String {
+ self.values.join("")
}
}
diff --git a/src/socket.rs b/src/socket.rs
index 3fdd4b1..3479548 100644
--- a/src/socket.rs
+++ b/src/socket.rs
@@ -1,6 +1,6 @@
use super::{
context::{Context, ContextError},
- parser, styles, Nodes,
+ parser, styles, Builder, Nodes,
};
use std::collections::HashMap;
use std::path::PathBuf;
@@ -109,11 +109,16 @@ impl<'a> Socket<'a> {
}
pub fn to_html(&self) -> String {
- self.nodes.to_html(
- &self.context,
- &self.fragments,
- &HashMap::new(),
- &self.styles,
- )
+ let builder = Builder::default();
+
+ self.nodes
+ .to_html(
+ builder,
+ &self.context,
+ &self.fragments,
+ &HashMap::new(),
+ &self.styles,
+ )
+ .result()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment