cmds.projects.lib.templates: Don't replace escaped markers
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m43s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m42s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 4m13s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m21s
CI / Packaging test (push) Successful in 0s

format_list_dict() substitutes {key} markers with str.replace(), which also
matches the marker inside ${key} and {{key}}, mangling text meant to
survive verbatim, e.g. the ${prefix} variable reference of a pkg-config
file. The built-in pkg-config template works around that with doubled
braces, which produces {/usr}-style output instead.

Replace the markers with a regular expression that skips markers preceded
by '$' or '{', and let the pkg-config template use the ${prefix} and
${exec_prefix} references pkg-config expects.

Extend the unit tests with marker escaping, value escaping, quoting,
indentation, and the rendering of the built-in templates.

Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2
Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-09-08 21:45:22 +02:00
commit 61280e4695
Signed by: Jan Lindemann
GPG key ID: 3750640C9E25DD61
2 changed files with 100 additions and 10 deletions

View file

@ -1,9 +1,14 @@
import os
import tempfile
from jw.pkg.cmds.projects.lib.templates import (
format_list_dict,
is_list_dict,
is_str_dict,
is_tuple_list,
merge_values,
render_values_to_list_dict,
tmpl_render,
)
# -- Type guards --
@ -47,6 +52,85 @@ try:
except Exception:
pass
# -- format_list_dict --
# A plain {key} marker is substituted
assert format_list_dict('a {key} b', {'key': ['x', 'y']}, False, ', ') == \
'a x, y b'
# ${key} is a literal, e.g. a pkg-config variable reference
assert format_list_dict(
'exec_prefix = ${prefix}', {'prefix': ['/usr']}, False, '\n'
) == 'exec_prefix = ${prefix}'
# {{key}} is a literal, too
assert format_list_dict('{{prefix}}', {'prefix': ['/usr']}, False, '\n') == \
'{{prefix}}'
# A ${key} next to a real marker: only the marker is substituted
assert format_list_dict('x = {p} ${p}', {'p': ['v']}, False, '\n') == \
'x = v ${p}'
# Backslashes in the value are not treated as regex replacements
assert format_list_dict('{p}', {'p': ['a\\b', 'c$1']}, False, '\n') == \
'a\\b\nc$1'
# li_quote wraps each list element in double quotes
assert format_list_dict('{p}', {'p': ['a', 'b']}, True, ',\n') == \
'"a",\n"b"'
# Multi-line values are re-indented to the marker position
assert format_list_dict(' {p}', {'p': ['x\ny']}, False, '\n') == ' x\n y'
# String values pass through unsplit
assert format_list_dict('{p}', {'p': 'xyz'}, False, '\n') == 'xyz'
# -- tmpl_render --
# The built-in pkg-config template renders a valid .pc file
expected_pc = (
'prefix = /usr\n'
'exec_prefix = ${prefix}\n'
'includedir = ${prefix}/include\n'
'libdir = ${exec_prefix}/lib\n'
'\n'
'Name: jw-pkg\n'
'Description: desc\n'
'Version: 1.0\n'
)
assert tmpl_render(
'pkg-config',
[{
'prefix': '/usr', 'name': 'jw-pkg', 'description': 'desc', 'version': '1.0'
}]
) == expected_pc
# A template file from the search path wins over the built-ins
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, 'my-tmpl')
with open(path, 'w') as f:
f.write('hello {who}\n')
assert tmpl_render(
'my-tmpl', [{
'who': 'world'
}], search_path = [tmp]
) == 'hello world\n'
# A missing template falls back to the built-ins
with tempfile.TemporaryDirectory() as tmp:
assert tmpl_render(
'pkg-config', [{
'prefix': '/usr'
}], search_path = [tmp]
).startswith('prefix = /usr\n')
# An unknown template without search path raises
try:
tmpl_render('no-such-template', [])
assert False, 'Should have raised'
except Exception:
pass
# -- merge_values --
# String values are merged as whole strings, not character by character
@ -70,4 +154,4 @@ assert merge_values(
'a': ['x', 'y', 'z'], 'b': ['w']
}
print('All templates merge tests passed')
print('All templates tests passed')