Fix newlines in yaml
This commit is contained in:
parent
94aeaf6e7a
commit
bbbbe33e54
4 changed files with 84 additions and 7 deletions
|
|
@ -157,6 +157,9 @@ class ErbTranslator:
|
||||||
return self.ruby_value(expr)
|
return self.ruby_value(expr)
|
||||||
|
|
||||||
def statement_to_erb(self, stmt: str) -> str:
|
def statement_to_erb(self, stmt: str) -> str:
|
||||||
|
if stmt.endswith("-"):
|
||||||
|
stmt = stmt[:-1].rstrip()
|
||||||
|
|
||||||
if stmt.startswith("for "):
|
if stmt.startswith("for "):
|
||||||
m = re.match(
|
m = re.match(
|
||||||
r"^for\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\s+([A-Za-z_][A-Za-z0-9_]*)$",
|
r"^for\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\s+([A-Za-z_][A-Za-z0-9_]*)$",
|
||||||
|
|
@ -172,7 +175,7 @@ class ErbTranslator:
|
||||||
if stmt == "endfor":
|
if stmt == "endfor":
|
||||||
if self.loop_stack:
|
if self.loop_stack:
|
||||||
self.loop_stack.pop()
|
self.loop_stack.pop()
|
||||||
return "<% end -%>"
|
return "<% end %>"
|
||||||
|
|
||||||
if stmt.startswith("if "):
|
if stmt.startswith("if "):
|
||||||
cond = stmt[3:].strip()
|
cond = stmt[3:].strip()
|
||||||
|
|
|
||||||
|
|
@ -246,8 +246,18 @@ class YamlHandler(DictLikeHandler):
|
||||||
return None, None
|
return None, None
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
def next_significant_line(index: int) -> tuple[int, str] | None:
|
||||||
|
for future_line in lines[index + 1 :]:
|
||||||
|
future_stripped = future_line.lstrip()
|
||||||
|
if not future_stripped.strip() or future_stripped.startswith("#"):
|
||||||
|
continue
|
||||||
|
return len(future_line) - len(future_stripped), future_stripped
|
||||||
|
return None
|
||||||
|
|
||||||
for line_index, raw_line in enumerate(lines):
|
for line_index, raw_line in enumerate(lines):
|
||||||
stripped = raw_line.lstrip()
|
stripped = raw_line.lstrip()
|
||||||
|
is_blank = not stripped.strip()
|
||||||
|
is_comment = stripped.startswith("#")
|
||||||
indent = len(raw_line) - len(stripped)
|
indent = len(raw_line) - len(stripped)
|
||||||
|
|
||||||
# If we're skipping lines inside a collection replaced by a loop,
|
# If we're skipping lines inside a collection replaced by a loop,
|
||||||
|
|
@ -261,7 +271,22 @@ class YamlHandler(DictLikeHandler):
|
||||||
# Stop only when a non-list item at the parent indentation appears,
|
# Stop only when a non-list item at the parent indentation appears,
|
||||||
# or when indentation moves above the parent collection.
|
# or when indentation moves above the parent collection.
|
||||||
if skip_until_indent is not None:
|
if skip_until_indent is not None:
|
||||||
if not stripped or stripped.startswith("#"):
|
if is_blank:
|
||||||
|
next_line = next_significant_line(line_index)
|
||||||
|
if next_line is None:
|
||||||
|
skip_until_indent = None
|
||||||
|
out_lines.append(raw_line)
|
||||||
|
else:
|
||||||
|
next_indent, next_stripped = next_line
|
||||||
|
still_in_collection = next_indent > skip_until_indent or (
|
||||||
|
next_indent == skip_until_indent
|
||||||
|
and next_stripped.startswith("- ")
|
||||||
|
)
|
||||||
|
if not still_in_collection:
|
||||||
|
skip_until_indent = None
|
||||||
|
out_lines.append(raw_line)
|
||||||
|
continue
|
||||||
|
if is_comment:
|
||||||
if indent <= skip_until_indent:
|
if indent <= skip_until_indent:
|
||||||
skip_until_indent = None
|
skip_until_indent = None
|
||||||
out_lines.append(raw_line)
|
out_lines.append(raw_line)
|
||||||
|
|
@ -277,7 +302,7 @@ class YamlHandler(DictLikeHandler):
|
||||||
continue # Skip this line
|
continue # Skip this line
|
||||||
|
|
||||||
# Blank or comment lines
|
# Blank or comment lines
|
||||||
if not stripped or stripped.startswith("#"):
|
if is_blank or is_comment:
|
||||||
out_lines.append(raw_line)
|
out_lines.append(raw_line)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -490,7 +515,7 @@ class YamlHandler(DictLikeHandler):
|
||||||
lines.append(j2.for_start(item_var, collection_var))
|
lines.append(j2.for_start(item_var, collection_var))
|
||||||
lines.append(j2.for_end())
|
lines.append(j2.for_end())
|
||||||
|
|
||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _dict_to_yaml_lines(
|
def _dict_to_yaml_lines(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,10 @@ def to_json(
|
||||||
return filtered(value, f"to_json({', '.join(args)})")
|
return filtered(value, f"to_json({', '.join(args)})")
|
||||||
|
|
||||||
|
|
||||||
def statement(value: str) -> str:
|
def statement(value: str, *, trim_right: bool = False) -> str:
|
||||||
"""Return a Jinja2 statement tag for an already-built statement body."""
|
"""Return a Jinja2 statement tag for an already-built statement body."""
|
||||||
|
if trim_right:
|
||||||
|
return f"{{% {value} -%}}"
|
||||||
return f"{{% {value} %}}"
|
return f"{{% {value} %}}"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -64,8 +66,8 @@ def for_start(item_var: str, collection_var: str) -> str:
|
||||||
return statement(f"for {item_var} in {collection_var}")
|
return statement(f"for {item_var} in {collection_var}")
|
||||||
|
|
||||||
|
|
||||||
def for_end() -> str:
|
def for_end(*, trim_right: bool = False) -> str:
|
||||||
return statement("endfor")
|
return statement("endfor", trim_right=trim_right)
|
||||||
|
|
||||||
|
|
||||||
def yaml_scalar_expression(var_name: str, raw_value: str | None = None) -> str:
|
def yaml_scalar_expression(var_name: str, raw_value: str | None = None) -> str:
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from jinjaturtle.core import (
|
||||||
analyze_loops,
|
analyze_loops,
|
||||||
flatten_config,
|
flatten_config,
|
||||||
generate_ansible_yaml,
|
generate_ansible_yaml,
|
||||||
|
generate_erb_template,
|
||||||
generate_jinja2_template,
|
generate_jinja2_template,
|
||||||
)
|
)
|
||||||
from jinjaturtle.handlers.yaml import YamlHandler
|
from jinjaturtle.handlers.yaml import YamlHandler
|
||||||
|
|
@ -169,6 +170,52 @@ def test_yaml_indentless_sequence_loop_roundtrips_semantically(tmp_path: Path):
|
||||||
assert " vagrant:" not in template
|
assert " vagrant:" not in template
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_loop_preserves_blank_separator_after_list(tmp_path: Path):
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
cases = [
|
||||||
|
(
|
||||||
|
"blank",
|
||||||
|
"require:\n - rubocop-performance\n - rubocop-rspec\n\nAllCops:\n NewCops: enable\n",
|
||||||
|
"\n - rubocop-rspec\n\nAllCops:",
|
||||||
|
"<% end %>\nAllCops:",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"no_blank",
|
||||||
|
"require:\n - rubocop-performance\n - rubocop-rspec\nAllCops:\n NewCops: enable\n",
|
||||||
|
"\n - rubocop-rspec\nAllCops:",
|
||||||
|
"<% end %>AllCops:",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for label, text, rendered_expected, erb_expected in cases:
|
||||||
|
path = tmp_path / f"{label}.yml"
|
||||||
|
path.write_text(text, encoding="utf-8")
|
||||||
|
|
||||||
|
fmt, parsed = parse_config(path)
|
||||||
|
loop_candidates = analyze_loops(fmt, parsed)
|
||||||
|
flat_items = flatten_config(fmt, parsed, loop_candidates)
|
||||||
|
defaults = yaml.safe_load(
|
||||||
|
generate_ansible_yaml("role", flat_items, loop_candidates)
|
||||||
|
)
|
||||||
|
|
||||||
|
template = generate_jinja2_template(
|
||||||
|
fmt, parsed, "role", original_text=text, loop_candidates=loop_candidates
|
||||||
|
)
|
||||||
|
rendered = Template(template).render(**defaults)
|
||||||
|
assert rendered_expected in rendered
|
||||||
|
|
||||||
|
erb_template = generate_erb_template(
|
||||||
|
fmt,
|
||||||
|
parsed,
|
||||||
|
"role",
|
||||||
|
original_text=text,
|
||||||
|
loop_candidates=loop_candidates,
|
||||||
|
flat_items=flat_items,
|
||||||
|
)
|
||||||
|
assert erb_expected in erb_template
|
||||||
|
|
||||||
|
|
||||||
def test_yaml_loop_preserves_following_top_level_comments(tmp_path: Path):
|
def test_yaml_loop_preserves_following_top_level_comments(tmp_path: Path):
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue