Add support for raw (unescaped HTML) blocks

This commit is contained in:
Olivier 'reivilibre' 2024-05-01 22:50:28 +01:00
parent 7171145aa5
commit 91ba94a38a
9 changed files with 174 additions and 13 deletions

15
Cargo.lock generated
View File

@ -780,6 +780,7 @@ dependencies = [
"hornbeam_grammar", "hornbeam_grammar",
"hornbeam_ir", "hornbeam_ir",
"html-escape", "html-escape",
"insta",
"itertools", "itertools",
"pollster", "pollster",
"thiserror", "thiserror",
@ -940,16 +941,15 @@ dependencies = [
[[package]] [[package]]
name = "insta" name = "insta"
version = "1.28.0" version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099" checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc"
dependencies = [ dependencies = [
"console", "console",
"lazy_static", "lazy_static",
"linked-hash-map", "linked-hash-map",
"serde", "serde",
"similar", "similar",
"yaml-rust",
] ]
[[package]] [[package]]
@ -2117,12 +2117,3 @@ checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View File

@ -15,6 +15,7 @@ pub enum Block {
ForBlock(ForBlock), ForBlock(ForBlock),
MatchBlock(MatchBlock), MatchBlock(MatchBlock),
Text(StringExpr), Text(StringExpr),
RawUnescapedHtml(StringExpr),
DefineExpandSlot(DefineExpandSlot), DefineExpandSlot(DefineExpandSlot),
DefineFragment(DefineFragment), DefineFragment(DefineFragment),
} }

View File

@ -10,6 +10,7 @@ BlockContent = _{
Element | Element |
IfBlock | IfBlock |
Text | Text |
RawUnescapedHtml |
DefineExpandSlot | DefineExpandSlot |
ForBlock | ForBlock |
MatchBlock | MatchBlock |
@ -34,6 +35,10 @@ Text = {
String ~ lineEnd String ~ lineEnd
} }
RawUnescapedHtml = {
"raw" ~ ws+ ~ String ~ lineEnd
}
IfBlock = { IfBlock = {
"if" ~ ws+ ~ IfCondition ~ lineEnd ~ NewBlock ~ "if" ~ ws+ ~ IfCondition ~ lineEnd ~ NewBlock ~
ElseBlock? ElseBlock?

View File

@ -202,6 +202,12 @@ impl HornbeamParser {
)?)) )?))
} }
fn RawUnescapedHtml(input: Node) -> PCResult<Block> {
Ok(Block::RawUnescapedHtml(HornbeamParser::String(
input.into_children().single()?,
)?))
}
fn String(input: Node) -> PCResult<StringExpr> { fn String(input: Node) -> PCResult<StringExpr> {
let mut pieces = Vec::new(); let mut pieces = Vec::new();
for node in input.into_children() { for node in input.into_children() {
@ -491,6 +497,7 @@ impl HornbeamParser {
Ok(match input.as_rule() { Ok(match input.as_rule() {
Rule::Element => Some(HornbeamParser::Element(input)?), Rule::Element => Some(HornbeamParser::Element(input)?),
Rule::Text => Some(HornbeamParser::Text(input)?), Rule::Text => Some(HornbeamParser::Text(input)?),
Rule::RawUnescapedHtml => Some(HornbeamParser::RawUnescapedHtml(input)?),
Rule::IfBlock => Some(HornbeamParser::IfBlock(input)?), Rule::IfBlock => Some(HornbeamParser::IfBlock(input)?),
Rule::ForBlock => Some(HornbeamParser::ForBlock(input)?), Rule::ForBlock => Some(HornbeamParser::ForBlock(input)?),
Rule::MatchBlock => Some(HornbeamParser::MatchBlock(input)?), Rule::MatchBlock => Some(HornbeamParser::MatchBlock(input)?),
@ -666,4 +673,17 @@ div
) )
.unwrap()); .unwrap());
} }
#[test]
fn raw() {
assert_yaml_snapshot!(parse_template(
r#"
div
span
raw "<u>wow $x ${$x} @wowage{}</u>"
"#,
"inp"
)
.unwrap());
}
} }

View File

@ -0,0 +1,45 @@
---
source: hornbeam_grammar/src/parser.rs
expression: "parse_template(r#\"\ndiv\n span\n raw \"<u>wow $x ${$x} @wowage{}</u>\"\n \"#,\n \"inp\").unwrap()"
---
blocks:
- HtmlElement:
name: div
children:
- HtmlElement:
name: span
children:
- RawUnescapedHtml:
pieces:
- Literal: "<u>wow "
- Interpolation:
Variable:
name: x
loc:
filename: inp
line: 4
column: 21
- Literal: " "
- Interpolation:
Variable:
name: x
loc:
filename: inp
line: 4
column: 26
- Literal: " @wowage{}</u>"
classes: []
dom_id: ~
attributes: {}
loc:
filename: inp
line: 3
column: 5
classes: []
dom_id: ~
attributes: {}
loc:
filename: inp
line: 2
column: 1

View File

@ -29,3 +29,6 @@ tracing = "0.1.37"
[features] [features]
default = ["fluent"] default = ["fluent"]
fluent = ["fluent-templates"] fluent = ["fluent-templates"]
[dev-dependencies]
insta = "1.38.0"

View File

@ -0,0 +1,56 @@
use bevy_reflect::Reflect;
use hornbeam_interpreter::{localisation::NoLocalisation, LoadedTemplates, Params};
use insta::assert_snapshot;
#[derive(Reflect)]
struct SimpleTestStruct {
wombat: u64,
apple: u64,
banana: String,
carrot: String,
}
fn simple_test_struct() -> SimpleTestStruct {
SimpleTestStruct {
wombat: 42,
apple: 78,
banana: "banana!!!".to_owned(),
carrot: "mmm CARROT".to_owned(),
}
}
fn simple_render(template: &str) -> String {
let mut templates = LoadedTemplates::new(NoLocalisation);
templates
.load_template_from_str("main", template, "main.hnb")
.expect("failed to load template");
let params = Params::default()
.set("sts", simple_test_struct())
.set("five", 5);
let prepared = templates.prepare("main", None, params, "en".to_owned());
prepared.render_to_string().unwrap()
}
#[test]
fn snapshot_001() {
assert_snapshot!(simple_render(
r#"
html
body
"this was a triumph :>"
br
raw "<u>making a note here, huge success</u>"
if $five == 5
"FIVE!!! $five"
br
if $five < 10
"five is less than ten!"
br
if $five > 5
"weird..."
"#
))
}

View File

@ -0,0 +1,5 @@
---
source: hornbeam_interpreter/tests/snapshots.rs
expression: "simple_render(r#\"\nhtml\n body\n \"this was a triumph :>\"\n br\n raw \"<u>making a note here, huge success</u>\"\n\n if $five == 5\n \"FIVE!!! $five\"\n br\n\n if $five < 10\n \"five is less than ten!\"\n br\n\n if $five > 5\n \"weird...\"\n \"#)"
---
<!DOCTYPE html><html><body>this was a triumph :&gt;<br><u>making a note here, huge success</u>FIVE!!! 5<br>five is less than ten!<br></body></html>

View File

@ -132,7 +132,7 @@ fn pull_out_entrypoints_from_block<'a>(
} }
} }
} }
Block::Text(_) | Block::DefineExpandSlot(_) => { /* nop */ } Block::Text(_) | Block::RawUnescapedHtml(_) | Block::DefineExpandSlot(_) => { /* nop */ }
} }
Ok(()) Ok(())
} }
@ -411,6 +411,41 @@ fn compile_ast_block_to_steps<'a>(
} }
} }
} }
Block::RawUnescapedHtml(text) => {
for piece in &text.pieces {
match piece {
StringPiece::Literal(lit) => {
instructions.push(Step {
def: StepDef::WriteLiteral {
text: lit.clone(),
escape: false,
},
locator: Locator::empty(),
});
}
StringPiece::Interpolation(expr) => {
instructions.push(Step {
def: StepDef::WriteEval {
expr: expr.clone(),
escape: false,
},
locator: Locator::empty(),
});
}
piece @ StringPiece::Localise { .. } => {
instructions.push(Step {
def: StepDef::WriteEval {
expr: Expression::StringExpr(StringExpr {
pieces: vec![piece.clone()],
}),
escape: false,
},
locator: Locator::empty(),
});
}
}
}
}
Block::DefineExpandSlot(slot) => { Block::DefineExpandSlot(slot) => {
instructions.push(Step { instructions.push(Step {
def: StepDef::CallSlotWithParentScope { def: StepDef::CallSlotWithParentScope {