Repository: mitsuhiko/minijinja (rust)

Issue:
Implement {% raw %}
MiniJinja currently lacks the quite useful `{% raw %}` block from Jinja2.

Pull request:
Added support for {% raw %} tag
Fixes #67

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65397bb..0ae1307 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 All notable changes to MiniJinja are documented here.
 
+# 0.17.0
+
+- Added support for `{% raw %}`. (#67)
+- Minimum Rust version moved up to 1.45.
+
 # 0.16.0
 
 - Added support for unpacking in `with` blocks. (#65)
diff --git a/Makefile b/Makefile
index 4f6b08f..2b78125 100644
--- a/Makefile
+++ b/Makefile
@@ -15,12 +15,6 @@ test:
 	@echo "CARGO TEST ALL FEATURES"
 	@cd minijinja; cargo test --all-features
 
-test-142:
-	@$(MAKE) run-tests FEATURES=$(TEST_FEATURES)
-	@$(MAKE) run-tests FEATURES=$(TEST_FEATURES),key_interning
-	@echo "CARGO TEST ALL FEATURES"
-	@cd minijinja; cargo test --all-features
-
 run-tests:
 	@rustup component add rustfmt 2> /dev/null
 	@echo "CARGO TESTS"
diff --git a/README.md b/README.md
index 84f98b2..3971332 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 [![Build Status](https://github.com/mitsuhiko/minijinja/workflows/Tests/badge.svg?branch=main)](https://github.com/mitsuhiko/minijinja/actions?query=workflow%3ATests)
 [![License](https://img.shields.io/github/license/mitsuhiko/minijinja)](https://github.com/mitsuhiko/minijinja/blob/main/LICENSE)
 [![Crates.io](https://img.shields.io/crates/d/minijinja.svg)](https://crates.io/crates/minijinja)
-[![rustc 1.43.0](https://img.shields.io/badge/rust-1.43%2B-orange.svg)](https://img.shields.io/badge/rust-1.43%2B-orange.svg)
+[![rustc 1.43.0](https://img.shields.io/badge/rust-1.45%2B-orange.svg)](https://img.shields.io/badge/rust-1.45%2B-orange.svg)
 [![Documentation](https://docs.rs/minijinja/badge.svg)](https://docs.rs/minijinja)
 
 </div>
diff --git a/minijinja/Cargo.toml b/minijinja/Cargo.toml
index 88ffe7b..0287b23 100644
--- a/minijinja/Cargo.toml
+++ b/minijinja/Cargo.toml
@@ -9,7 +9,7 @@ homepage = "https://github.com/mitsuhiko/minijinja"
 repository = "https://github.com/mitsuhiko/minijinja"
 keywords = ["jinja", "jinja2", "templates"]
 readme = "README.md"
-rust-version = "1.43"
+rust-version = "1.45"
 
 [package.metadata.docs.rs]
 features = ["source", "json", "urlencode"]
diff --git a/minijinja/src/lexer.rs b/minijinja/src/lexer.rs
index d349276..d34d117 100644
--- a/minijinja/src/lexer.rs
+++ b/minijinja/src/lexer.rs
@@ -22,6 +22,29 @@ fn find_marker(a: &str) -> Option<usize> {
     }
 }
 
+fn skip_basic_tag(block_str: &str, name: &str) -> Option<usize> {
+    let mut ptr = block_str;
+
+    if let Some(rest) = ptr.strip_prefix('-') {
+        ptr = rest;
+    }
+    while let Some(rest) = ptr.strip_prefix(|x: char| x.is_ascii_whitespace()) {
+        ptr = rest;
+    }
+
+    ptr = ptr.strip_prefix(name)?;
+
+    while let Some(rest) = ptr.strip_prefix(|x: char| x.is_ascii_whitespace()) {
+        ptr = rest;
+    }
+    if let Some(rest) = ptr.strip_prefix('-') {
+        ptr = rest;
+    }
+    ptr = ptr.strip_prefix("%}")?;
+
+    Some(block_str.len() - ptr.len())
+}
+
 /// Tokenizes without whitespace handling.
 fn tokenize_raw(
     input: &str,
@@ -182,6 +205,22 @@ fn tokenize_raw(
                         return Some(Ok((Token::VariableStart(ws), span!(old_loc))));
                     }
                     Some("{%") => {
+                        // raw blocks require some special handling.  If we are at the beginning of a raw
+                        // block we want to skip everything until {% endraw %} completely ignoring iterior
+                        // syntax and emit the entire raw block as TemplateData.
+                        if let Some(mut ptr) = skip_basic_tag(&rest[2..], "raw") {
+                            ptr += 2;
+                            while let Some(block) = memstr(&rest.as_bytes()[ptr..], b"{%") {
+                                ptr += block + 2;
+                                if let Some(endraw) = skip_basic_tag(&rest[ptr..], "endraw") {
+                                    let result = &rest[..ptr + endraw];
+                                    advance!(ptr + endraw);
+                                    return Some(Ok((Token::TemplateData(result), span!(old_loc))));
+                                }
+                            }
+                            syntax_error!("unexpected end of raw block");
+                        }
+
                         let ws = if rest.as_bytes().get(2) == Some(&b'-') {
                             advance!(3);
                             true
@@ -189,6 +228,7 @@ fn tokenize_raw(
                             advance!(2);
                             false
                         };
+
                         stack.push(LexerState::InBlock);
                         return Some(Ok((Token::BlockStart(ws), span!(old_loc))));
                     }
@@ -400,3 +440,11 @@ fn test_find_marker() {
     assert!(find_marker("foo {").is_none());
     assert_eq!(find_marker("foo {{"), Some(4));
 }
+
+#[test]
+fn test_is_basic_tag() {
+    assert_eq!(skip_basic_tag(" raw %}", "raw"), Some(7));
+    assert_eq!(skip_basic_tag(" raw %}", "endraw"), None);
+    assert_eq!(skip_basic_tag("  raw  %}", "raw"), Some(9));
+    assert_eq!(skip_basic_tag("-  raw  -%}", "raw"), Some(11));
+}
diff --git a/minijinja/src/syntax.rs b/minijinja/src/syntax.rs
index 1af320a..a1aa9f4 100644
--- a/minijinja/src/syntax.rs
+++ b/minijinja/src/syntax.rs
@@ -20,6 +20,7 @@
 //!   - [`{% with %}`](#-with-)
 //!   - [`{% filter %}`](#-filter-)
 //!   - [`{% autoescape %}`](#-autoescape-)
+//!   - [`{% raw %}`](#-raw-)
 //!
 //! </details>
 //!
@@ -433,5 +434,23 @@
 //! ```
 //!
 //! After an `endautoescape` the behavior is reverted to what it was before.
+//!
+//! ## `{% raw %}`
+//!
+//! A raw block is a special construct that lets you ignore the embedded template
+//! syntax.  This is particularly useful if a segment of template code would
+//! otherwise require constant escaping with things like `{{ "{{" }}`:
+//!
+//! Example:
+//!
+//! ```jinja
+//! {% raw %}
+//! <ul>
+//! {% for item in seq %}
+//!     <li>{{ item }}</li>
+//! {% endfor %}
+//! </ul>
+//! {% endraw %}
+//! ```
 
 // this is just for docs


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a705712..05d04cf 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -3,7 +3,7 @@ name: Tests
 on: [push]
 
 jobs:
-  build-latest:
+  test-latest:
     name: Test on Latest
     runs-on: ubuntu-latest
 
@@ -18,28 +18,28 @@ jobs:
         run: make test
 
   build-old-stable:
-    name: Build on 1.43.0
+    name: Build on 1.45.0
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v1
       - uses: actions-rs/toolchain@v1
         with:
-          toolchain: 1.43.0
+          toolchain: 1.45.0
           profile: minimal
           override: true
       - name: Build
         run: cargo build --features=unstable_machinery,builtins,source,json,urlencode,debug,internal_debug
         working-directory: ./minijinja
 
-  build-stable:
-    name: Test on 1.46.0
+  test-stable:
+    name: Test on 1.56.1
     runs-on: ubuntu-latest
 
     steps:
       - uses: actions/checkout@v1
       - uses: actions-rs/toolchain@v1
         with:
-          toolchain: 1.46.0
+          toolchain: 1.56.1
           profile: minimal
           override: true
       - name: Test
diff --git a/minijinja/tests/lexer-inputs/raw.txt b/minijinja/tests/lexer-inputs/raw.txt
new file mode 100644
index 0000000..eaa2d8f
--- /dev/null
+++ b/minijinja/tests/lexer-inputs/raw.txt
@@ -0,0 +1,5 @@
+before {% raw %} this is a {{ raw }} {% block %} {% endraw %} after
+
+before {%- raw %} this is a {{ raw }} {% block %} {% endraw -%} after
+
+before {%- raw -%} this is a {{ raw }} {% block %} {%- endraw -%} after
\ No newline at end of file
diff --git a/minijinja/tests/snapshots/test_lexer__lexer@raw.txt.snap b/minijinja/tests/snapshots/test_lexer__lexer@raw.txt.snap
new file mode 100644
index 0000000..309a663
--- /dev/null
+++ b/minijinja/tests/snapshots/test_lexer__lexer@raw.txt.snap
@@ -0,0 +1,14 @@
+---
+source: minijinja/tests/test_lexer.rs
+expression: "&tokens"
+input_file: minijinja/tests/lexer-inputs/raw.txt
+---
+[
+    TEMPLATE_DATA("before "),
+    TEMPLATE_DATA("{% raw %} this is a {{ raw }} {% block %} {% endraw %}"),
+    TEMPLATE_DATA(" after\n\nbefore "),
+    TEMPLATE_DATA("{%- raw %} this is a {{ raw }} {% block %} {% endraw -%}"),
+    TEMPLATE_DATA(" after\n\nbefore "),
+    TEMPLATE_DATA("{%- raw -%} this is a {{ raw }} {% block %} {%- endraw -%}"),
+    TEMPLATE_DATA(" after"),
+]