diff --git a/include/inja/config.hpp b/include/inja/config.hpp index 35ea2a5..01bfa6d 100644 --- a/include/inja/config.hpp +++ b/include/inja/config.hpp @@ -25,7 +25,9 @@ struct LexerConfig { std::string expression_close {"}}"}; std::string expression_close_force_rstrip {"-}}"}; std::string comment_open {"{#"}; + std::string comment_open_force_lstrip {"{#-"}; std::string comment_close {"#}"}; + std::string comment_close_force_rstrip {"-#}"}; std::string open_chars {"#{"}; bool trim_blocks {false}; @@ -54,6 +56,9 @@ struct LexerConfig { if (open_chars.find(comment_open[0]) == std::string::npos) { open_chars += comment_open[0]; } + if (open_chars.find(comment_open_force_lstrip[0]) == std::string::npos) { + open_chars += comment_open_force_lstrip[0]; + } } }; diff --git a/include/inja/environment.hpp b/include/inja/environment.hpp index ea21efe..2b6248b 100644 --- a/include/inja/environment.hpp +++ b/include/inja/environment.hpp @@ -73,7 +73,9 @@ public: /// Sets the opener and closer for template comments void set_comment(const std::string &open, const std::string &close) { lexer_config.comment_open = open; + lexer_config.comment_open_force_lstrip = open + "-"; lexer_config.comment_close = close; + lexer_config.comment_close_force_rstrip = "-" + close; lexer_config.update_open_chars(); } diff --git a/include/inja/lexer.hpp b/include/inja/lexer.hpp index 3be56fa..158f8b2 100644 --- a/include/inja/lexer.hpp +++ b/include/inja/lexer.hpp @@ -28,6 +28,7 @@ class Lexer { StatementStartForceLstrip, StatementBody, CommentStart, + CommentStartForceLstrip, CommentBody, }; @@ -243,7 +244,7 @@ class Lexer { } if (pos < m_in.size()) { - char ch = m_in[pos]; + const char ch = m_in[pos]; if (ch == '\n') { pos += 1; } else if (ch == '\r') { @@ -258,7 +259,7 @@ class Lexer { static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text) { nonstd::string_view result = text; while (!result.empty()) { - char ch = result.back(); + const char ch = result.back(); if (ch == ' ' || ch == '\t') { result.remove_suffix(1); } else if (ch == '\n' || ch == '\r') { @@ -331,8 +332,13 @@ public: must_lstrip = config.lstrip_blocks; } } else if (inja::string_view::starts_with(open_str, config.comment_open)) { - state = State::CommentStart; - must_lstrip = config.lstrip_blocks; + if (inja::string_view::starts_with(open_str, config.comment_open_force_lstrip)) { + state = State::CommentStartForceLstrip; + must_lstrip = true; + } else { + state = State::CommentStart; + must_lstrip = config.lstrip_blocks; + } } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) { state = State::LineStart; } else { @@ -385,6 +391,11 @@ public: pos += config.comment_open.size(); return make_token(Token::Kind::CommentOpen); } + case State::CommentStartForceLstrip: { + state = State::CommentBody; + pos += config.comment_open_force_lstrip.size(); + return make_token(Token::Kind::CommentOpen); + } case State::ExpressionBody: return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip); case State::LineBody: @@ -393,16 +404,21 @@ public: return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks); case State::CommentBody: { // fast-scan to comment close - size_t end = m_in.substr(pos).find(config.comment_close); + const size_t end = m_in.substr(pos).find(config.comment_close); if (end == nonstd::string_view::npos) { pos = m_in.size(); return make_token(Token::Kind::Eof); } + + // Check for trim pattern + const bool must_rstrip = inja::string_view::starts_with(m_in.substr(pos + end - 1), config.comment_close_force_rstrip); + // return the entire comment in the close token state = State::Text; pos += end + config.comment_close.size(); Token tok = make_token(Token::Kind::CommentClose); - if (config.trim_blocks) { + + if (must_rstrip || config.trim_blocks) { skip_whitespaces_and_first_newline(); } return tok; diff --git a/test/test-renderer.cpp b/test/test-renderer.cpp index 213722c..db6c00c 100644 --- a/test/test-renderer.cpp +++ b/test/test-renderer.cpp @@ -214,23 +214,29 @@ TEST_CASE("templates") { CHECK(env.render(" {{- name -}} \n ", data) == "Peter"); CHECK(env.render("Test\n {{- name }} ", data) == "Test\nPeter "); CHECK(env.render(" {{ name }}\n ", data) == " Peter\n "); + CHECK(env.render("{{ name }}{# name -#} !", data) == "Peter!"); + CHECK(env.render(" {#- name -#} !", data) == "!"); // Nothing will be stripped if there are other characters before the start of the block. CHECK(env.render(". {%- if is_happy %}{{ name }}{% endif -%}\n", data) == ". Peter"); + CHECK(env.render(". {#- comment -#}\n.", data) == ". ."); env.set_lstrip_blocks(true); + CHECK(env.render("Hello {{ name }}!", data) == "Hello Peter!"); CHECK(env.render(" {% if is_happy %}{{ name }}{% endif %}", data) == "Peter"); CHECK(env.render(" {% if is_happy %}{{ name }}{% endif %} ", data) == "Peter "); CHECK(env.render(" {% if is_happy %}{{ name }}{% endif -%} ", data) == "Peter"); CHECK(env.render(" {%+ if is_happy %}{{ name }}{% endif %}", data) == " Peter"); CHECK(env.render("\n {%+ if is_happy %}{{ name }}{% endif -%} ", data) == "\n Peter"); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %}\n", data) == "Peter\n"); + CHECK(env.render(" {# comment #}", data) == ""); env.set_trim_blocks(true); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %}", data) == "Peter"); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %}\n", data) == "Peter"); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %} \n.", data) == "Peter."); CHECK(env.render("{%- if is_happy %}{{ name }}{% endif -%} \n.", data) == "Peter."); + CHECK(env.render(" {# comment #} \n.", data) == "."); } }