diff --git a/src/jinjaturtle/erb.py b/src/jinjaturtle/erb.py index 2c5788e..445ec51 100644 --- a/src/jinjaturtle/erb.py +++ b/src/jinjaturtle/erb.py @@ -157,7 +157,7 @@ class ErbTranslator: return self.ruby_value(expr) def statement_to_erb(self, stmt: str) -> str: - if stmt.endswith("-"): + if stmt.endswith(("-", "+")): stmt = stmt[:-1].rstrip() if stmt.startswith("for "): diff --git a/src/jinjaturtle/handlers/yaml.py b/src/jinjaturtle/handlers/yaml.py index 19013f8..1ad9bf3 100644 --- a/src/jinjaturtle/handlers/yaml.py +++ b/src/jinjaturtle/handlers/yaml.py @@ -510,10 +510,10 @@ class YamlHandler(DictLikeHandler): # into the rendered YAML and nesting the next top-level key. lines.append(f"{j2.for_start(item_var, collection_var)}{item_lines[0]}") lines.extend(item_lines[1:]) - lines.append(j2.for_end()) + lines.append(j2.for_end(keep_trailing_newline=True)) else: lines.append(j2.for_start(item_var, collection_var)) - lines.append(j2.for_end()) + lines.append(j2.for_end(keep_trailing_newline=True)) return "\n".join(lines) diff --git a/src/jinjaturtle/j2.py b/src/jinjaturtle/j2.py index 498a39e..13531f5 100644 --- a/src/jinjaturtle/j2.py +++ b/src/jinjaturtle/j2.py @@ -43,10 +43,19 @@ def to_json( return filtered(value, f"to_json({', '.join(args)})") -def statement(value: str, *, trim_right: bool = False) -> str: +def statement( + value: str, + *, + trim_right: bool = False, + keep_trailing_newline: bool = False, +) -> str: """Return a Jinja2 statement tag for an already-built statement body.""" + if trim_right and keep_trailing_newline: + raise ValueError("trim_right and keep_trailing_newline are mutually exclusive") if trim_right: return f"{{% {value} -%}}" + if keep_trailing_newline: + return f"{{% {value} +%}}" return f"{{% {value} %}}" @@ -66,8 +75,12 @@ def for_start(item_var: str, collection_var: str) -> str: return statement(f"for {item_var} in {collection_var}") -def for_end(*, trim_right: bool = False) -> str: - return statement("endfor", trim_right=trim_right) +def for_end(*, trim_right: bool = False, keep_trailing_newline: bool = False) -> str: + return statement( + "endfor", + trim_right=trim_right, + keep_trailing_newline=keep_trailing_newline, + ) def yaml_scalar_expression(var_name: str, raw_value: str | None = None) -> str: diff --git a/tests/test_yaml_handler.py b/tests/test_yaml_handler.py index 5c70e27..a217e8a 100644 --- a/tests/test_yaml_handler.py +++ b/tests/test_yaml_handler.py @@ -217,7 +217,7 @@ def test_yaml_loop_preserves_blank_separator_after_list(tmp_path: Path): def test_yaml_loop_preserves_following_top_level_comments(tmp_path: Path): - from jinja2 import Template + from jinja2 import Environment, Template from jinjaturtle.core import analyze_loops @@ -248,10 +248,15 @@ def test_yaml_loop_preserves_following_top_level_comments(tmp_path: Path): fmt, parsed, "role", original_text=text, loop_candidates=loop_candidates ) rendered = Template(template).render(**defaults) + ansible_rendered = ( + Environment(trim_blocks=True).from_string(template).render(**defaults) + ) assert "# Offense count: 2" in rendered assert "# This cop supports unsafe autocorrection" in rendered + assert "spec.rb\n\n# Offense count: 2" in ansible_rendered assert yaml.safe_load(rendered) == yaml.safe_load(text) + assert yaml.safe_load(ansible_rendered) == yaml.safe_load(text) def test_yaml_scalar_loop_preserves_quoted_list_items(tmp_path: Path):