Improvements
Some checks failed
CI / test (push) Failing after 33s
Lint / test (push) Successful in 23s
Trivy / test (push) Successful in 22s

* Preserve comments in Jinja2 templates
 * Handle truthy/falsy statements better
 * Handle params that have an empty value (php.ini is notorious)
 * Add indentation to yaml and also starting --- so yamllint passes
This commit is contained in:
Miguel Jacq 2025-11-25 16:35:18 +11:00
parent 2be1e9e895
commit f992da47ee
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
5 changed files with 396 additions and 13 deletions

View file

@ -1,7 +1,7 @@
from __future__ import annotations
from pathlib import Path
import configparser
import pytest
import yaml
@ -69,7 +69,7 @@ def test_toml_sample_roundtrip():
assert fmt == "toml"
flat_items = flatten_config(fmt, parsed)
assert flat_items, "Expected at least one flattened item from TOML sample"
assert flat_items
defaults_yaml = generate_defaults_yaml("jinjaturtle", flat_items)
defaults = yaml.safe_load(defaults_yaml)
@ -84,10 +84,16 @@ def test_toml_sample_roundtrip():
assert key == key.lower()
assert " " not in key
# template generation
template = generate_template(fmt, parsed, "jinjaturtle")
# template generation **now with original_text**
original_text = toml_path.read_text(encoding="utf-8")
template = generate_template(
fmt, parsed, "jinjaturtle", original_text=original_text
)
assert isinstance(template, str)
assert template.strip(), "Template for TOML sample should not be empty"
assert template.strip()
# comments from the original file should now be preserved
assert "# This is a TOML document" in template
# each default variable name should appear in the template as a Jinja placeholder
for var_name in defaults:
@ -120,7 +126,9 @@ def test_ini_php_sample_roundtrip():
assert " " not in key
# template generation
template = generate_template(fmt, parsed, "php")
original_text = ini_path.read_text(encoding="utf-8")
template = generate_template(fmt, parsed, "php", original_text=original_text)
assert "; About this file" in template
assert isinstance(template, str)
assert template.strip(), "Template for php.ini sample should not be empty"
@ -189,3 +197,92 @@ def test_generate_template_type_and_format_errors():
# unsupported format
with pytest.raises(ValueError):
generate_template("yaml", parsed=None, role_prefix="role")
# unsupported format even when original_text is provided
with pytest.raises(ValueError):
generate_template(
"yaml",
parsed=None,
role_prefix="role",
original_text="foo=bar",
)
def test_normalize_default_value_true_false_strings():
# 'true'/'false' strings should be preserved as strings and double-quoted in YAML.
flat_items = [
(("section", "foo"), "true"),
(("section", "bar"), "FALSE"),
]
defaults_yaml = generate_defaults_yaml("role", flat_items)
data = yaml.safe_load(defaults_yaml)
assert data["role_section_foo"] == "true"
assert data["role_section_bar"] == "FALSE"
def test_split_inline_comment_handles_quoted_hash():
# The '#' inside quotes should not start a comment; the one outside should.
text = " 'foo # not comment' # real"
value, comment = core._split_inline_comment(text, {"#"})
assert "not comment" in value
assert comment.strip() == "# real"
def test_generate_template_fallback_toml_and_ini():
# When original_text is not provided, generate_template should use the
# older fallback generators based on the parsed structures.
parsed_toml = {
"title": "Example",
"server": {"port": 8080, "host": "127.0.0.1"},
"logging": {
"file": {"path": "/tmp/app.log"}
}, # nested table to hit recursive walk
}
tmpl_toml = generate_template("toml", parsed=parsed_toml, role_prefix="role")
assert "[server]" in tmpl_toml
assert "role_server_port" in tmpl_toml
assert "[logging]" in tmpl_toml or "[logging.file]" in tmpl_toml
parser = configparser.ConfigParser()
# foo is quoted in the INI text to hit the "preserve quotes" branch
parser["section"] = {"foo": '"bar"', "num": "42"}
tmpl_ini = generate_template("ini", parsed=parser, role_prefix="role")
assert "[section]" in tmpl_ini
assert "role_section_foo" in tmpl_ini
assert '"{{ role_section_foo }}"' in tmpl_ini # came from quoted INI value
def test_generate_ini_template_from_text_edge_cases():
# Cover CRLF newlines, lines without '=', and lines with no key before '='.
text = "[section]\r\nkey=value\r\nnoequals\r\n = bare\r\n"
tmpl = core._generate_ini_template_from_text("role", text)
# We don't care about exact formatting here, just that it runs and
# produces some reasonable output.
assert "[section]" in tmpl
assert "role_section_key" in tmpl
# The "noequals" line should be preserved as-is.
assert "noequals" in tmpl
# The " = bare" line has no key and should be left untouched.
assert " = bare" in tmpl
def test_generate_toml_template_from_text_edge_cases():
# Cover CRLF newlines, lines without '=', empty keys, and inline tables
# that both parse successfully and fail parsing.
text = (
"# comment\r\n"
"[table]\r\n"
"noequals\r\n"
" = 42\r\n"
'inline_good = { name = "abc", value = 1 }\r\n'
"inline_bad = { invalid = }\r\n"
)
tmpl = core._generate_toml_template_from_text("role", text)
# The good inline table should expand into two separate variables.
assert "role_table_inline_good_name" in tmpl
assert "role_table_inline_good_value" in tmpl
# The bad inline table should fall back to scalar handling.
assert "role_table_inline_bad" in tmpl
# Ensure the lines without '=' / empty key were handled without exploding.
assert "[table]" in tmpl
assert "noequals" in tmpl