From 91ba94a38ad5769f7ae2e8664ebbc477f823afa0 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 1 May 2024 22:50:28 +0100 Subject: [PATCH] Add support for raw (unescaped HTML) blocks --- Cargo.lock | 15 +---- hornbeam_grammar/src/ast.rs | 1 + hornbeam_grammar/src/hornbeam.pest | 5 ++ hornbeam_grammar/src/parser.rs | 20 +++++++ .../hornbeam_grammar__parser__tests__raw.snap | 45 +++++++++++++++ hornbeam_interpreter/Cargo.toml | 3 + hornbeam_interpreter/tests/snapshots.rs | 56 +++++++++++++++++++ .../snapshots/snapshots__snapshot_001.snap | 5 ++ hornbeam_ir/src/ast_to_ir.rs | 37 +++++++++++- 9 files changed, 174 insertions(+), 13 deletions(-) create mode 100644 hornbeam_grammar/src/snapshots/hornbeam_grammar__parser__tests__raw.snap create mode 100644 hornbeam_interpreter/tests/snapshots.rs create mode 100644 hornbeam_interpreter/tests/snapshots/snapshots__snapshot_001.snap diff --git a/Cargo.lock b/Cargo.lock index c5219d7..053f3bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,6 +780,7 @@ dependencies = [ "hornbeam_grammar", "hornbeam_ir", "html-escape", + "insta", "itertools", "pollster", "thiserror", @@ -940,16 +941,15 @@ dependencies = [ [[package]] name = "insta" -version = "1.28.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099" +checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" dependencies = [ "console", "lazy_static", "linked-hash-map", "serde", "similar", - "yaml-rust", ] [[package]] @@ -2117,12 +2117,3 @@ checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" dependencies = [ "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", -] diff --git a/hornbeam_grammar/src/ast.rs b/hornbeam_grammar/src/ast.rs index 3f3269c..7a3d33f 100644 --- a/hornbeam_grammar/src/ast.rs +++ b/hornbeam_grammar/src/ast.rs @@ -15,6 +15,7 @@ pub enum Block { ForBlock(ForBlock), MatchBlock(MatchBlock), Text(StringExpr), + RawUnescapedHtml(StringExpr), DefineExpandSlot(DefineExpandSlot), DefineFragment(DefineFragment), } diff --git a/hornbeam_grammar/src/hornbeam.pest b/hornbeam_grammar/src/hornbeam.pest index 2360878..3cd06ec 100644 --- a/hornbeam_grammar/src/hornbeam.pest +++ b/hornbeam_grammar/src/hornbeam.pest @@ -10,6 +10,7 @@ BlockContent = _{ Element | IfBlock | Text | + RawUnescapedHtml | DefineExpandSlot | ForBlock | MatchBlock | @@ -34,6 +35,10 @@ Text = { String ~ lineEnd } +RawUnescapedHtml = { + "raw" ~ ws+ ~ String ~ lineEnd +} + IfBlock = { "if" ~ ws+ ~ IfCondition ~ lineEnd ~ NewBlock ~ ElseBlock? diff --git a/hornbeam_grammar/src/parser.rs b/hornbeam_grammar/src/parser.rs index 425c5b1..5210c01 100644 --- a/hornbeam_grammar/src/parser.rs +++ b/hornbeam_grammar/src/parser.rs @@ -202,6 +202,12 @@ impl HornbeamParser { )?)) } + fn RawUnescapedHtml(input: Node) -> PCResult { + Ok(Block::RawUnescapedHtml(HornbeamParser::String( + input.into_children().single()?, + )?)) + } + fn String(input: Node) -> PCResult { let mut pieces = Vec::new(); for node in input.into_children() { @@ -491,6 +497,7 @@ impl HornbeamParser { Ok(match input.as_rule() { Rule::Element => Some(HornbeamParser::Element(input)?), Rule::Text => Some(HornbeamParser::Text(input)?), + Rule::RawUnescapedHtml => Some(HornbeamParser::RawUnescapedHtml(input)?), Rule::IfBlock => Some(HornbeamParser::IfBlock(input)?), Rule::ForBlock => Some(HornbeamParser::ForBlock(input)?), Rule::MatchBlock => Some(HornbeamParser::MatchBlock(input)?), @@ -666,4 +673,17 @@ div ) .unwrap()); } + + #[test] + fn raw() { + assert_yaml_snapshot!(parse_template( + r#" +div + span + raw "wow $x ${$x} @wowage{}" + "#, + "inp" + ) + .unwrap()); + } } diff --git a/hornbeam_grammar/src/snapshots/hornbeam_grammar__parser__tests__raw.snap b/hornbeam_grammar/src/snapshots/hornbeam_grammar__parser__tests__raw.snap new file mode 100644 index 0000000..48fe2b0 --- /dev/null +++ b/hornbeam_grammar/src/snapshots/hornbeam_grammar__parser__tests__raw.snap @@ -0,0 +1,45 @@ +--- +source: hornbeam_grammar/src/parser.rs +expression: "parse_template(r#\"\ndiv\n span\n raw \"wow $x ${$x} @wowage{}\"\n \"#,\n \"inp\").unwrap()" +--- +blocks: + - HtmlElement: + name: div + children: + - HtmlElement: + name: span + children: + - RawUnescapedHtml: + pieces: + - Literal: "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{}" + classes: [] + dom_id: ~ + attributes: {} + loc: + filename: inp + line: 3 + column: 5 + classes: [] + dom_id: ~ + attributes: {} + loc: + filename: inp + line: 2 + column: 1 + diff --git a/hornbeam_interpreter/Cargo.toml b/hornbeam_interpreter/Cargo.toml index 057822c..f481c97 100644 --- a/hornbeam_interpreter/Cargo.toml +++ b/hornbeam_interpreter/Cargo.toml @@ -29,3 +29,6 @@ tracing = "0.1.37" [features] default = ["fluent"] fluent = ["fluent-templates"] + +[dev-dependencies] +insta = "1.38.0" diff --git a/hornbeam_interpreter/tests/snapshots.rs b/hornbeam_interpreter/tests/snapshots.rs new file mode 100644 index 0000000..ba73a62 --- /dev/null +++ b/hornbeam_interpreter/tests/snapshots.rs @@ -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 "making a note here, huge success" + + if $five == 5 + "FIVE!!! $five" + br + + if $five < 10 + "five is less than ten!" + br + + if $five > 5 + "weird..." + "# + )) +} diff --git a/hornbeam_interpreter/tests/snapshots/snapshots__snapshot_001.snap b/hornbeam_interpreter/tests/snapshots/snapshots__snapshot_001.snap new file mode 100644 index 0000000..13dd43f --- /dev/null +++ b/hornbeam_interpreter/tests/snapshots/snapshots__snapshot_001.snap @@ -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 \"making a note here, huge success\"\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 \"#)" +--- +this was a triumph :>
making a note here, huge successFIVE!!! 5
five is less than ten!
diff --git a/hornbeam_ir/src/ast_to_ir.rs b/hornbeam_ir/src/ast_to_ir.rs index 51a554d..28361dd 100644 --- a/hornbeam_ir/src/ast_to_ir.rs +++ b/hornbeam_ir/src/ast_to_ir.rs @@ -132,7 +132,7 @@ fn pull_out_entrypoints_from_block<'a>( } } } - Block::Text(_) | Block::DefineExpandSlot(_) => { /* nop */ } + Block::Text(_) | Block::RawUnescapedHtml(_) | Block::DefineExpandSlot(_) => { /* nop */ } } 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) => { instructions.push(Step { def: StepDef::CallSlotWithParentScope {