Improve Python config file template substitution #8

Merged
Jan Lindemann merged 10 commits from jan/feature/20260609-pyproject-toml-add-mypypath into master 2026-06-09 08:13:09 +02:00 AGit
3 changed files with 103 additions and 20 deletions
Showing only changes of commit 81e71bc5c1 - Show all commits

cmds.projects.lib.templates.tmpl_render(): RenderValues

tmpl_render()'s "values" argument currently understands dict[str,str|list[str]]. Enhance that to understand the broader RenderValues type, which also includes Iterable[tuple[str, str]], as produced by argparse.add_argument(action='append').

This commit also adds proper type-checking for the values argument. Before, its type had gone unchecked entirely.

Signed-off-by: Jan Lindemann <jan@janware.com>
Jan Lindemann 2026-06-04 07:27:52 +02:00
Signed by: Jan Lindemann
GPG key ID: 3750640C9E25DD61

View file

@ -65,7 +65,7 @@ class CmdCreateFile(Cmd): # export
self.__update_dict(values, extra_fields) self.__update_dict(values, extra_fields)
return tmpl_render( return tmpl_render(
'pyrightconfig.json', values, li_quote = True, li_delimiter = ',\n' 'pyrightconfig.json', [values], li_quote = True, li_delimiter = ',\n'
) )
def __init__(self, parent: Parent) -> None: def __init__(self, parent: Parent) -> None:

View file

@ -1,8 +1,9 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING
from .Cmd import Cmd, Parent from .Cmd import Cmd, Parent
from .lib.templates import tmpl_render from .lib.templates import tmpl_render
from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from argparse import ArgumentParser, Namespace from argparse import ArgumentParser, Namespace
@ -61,24 +62,26 @@ class CmdCreatePkgConfig(Cmd): # export
contents = tmpl_render( contents = tmpl_render(
'pkg-config', 'pkg-config',
{ [
'prefix': args.prefix, {
'name': args.name, 'prefix': args.prefix,
'description': merged['summary'], 'name': args.name,
'version': args.version, 'description': merged['summary'] or '',
}, 'version': args.version,
}
]
) )
if args.cflags is not None: if args.cflags is not None:
contents += f'Cflags: {args.cflags}\n' contents += f'Cflags: {args.cflags}\n'
if args.libflags is not None: if args.libflags is not None:
contents += f'Libs: {args.libflags}\n' contents += f'Libs: {args.libflags}\n'
if merged['requires_run'] is not None: val = merged.get('requires_run')
contents += f'Requires: {self.__cleanup_requires(merged["requires_run"])}' if val is not None:
if merged['requires_build'] is not None: contents += f'Requires: {self.__cleanup_requires(val)}'
contents += ( val = merged.get('requires_build')
f'Requires.private: {self.__cleanup_requires(merged["requires_build"])}' if val is not None:
) contents += (f'Requires.private: {self.__cleanup_requires(val)}')
# not sure what to do with requires_devel # not sure what to do with requires_devel
print(contents) print(contents)

View file

@ -1,7 +1,79 @@
import textwrap import textwrap
from typing import Iterable, TypeAlias, TypeGuard
def format_lines( TupleList: TypeAlias = Iterable[tuple[str, str]]
template: str, values: dict[str, str], li_quote: bool, li_delimiter: str ListDict: TypeAlias = dict[str, list[str]]
StrDict: TypeAlias = dict[str, str]
RenderValues: TypeAlias = ListDict | StrDict | TupleList
def is_str_dict(values: RenderValues) -> TypeGuard[StrDict]:
if not isinstance(values, dict):
return False
for key, val in values.items():
if not isinstance(key, str):
return False
if not isinstance(val, str):
return False
return True
def is_list_dict(values: RenderValues) -> TypeGuard[ListDict]:
if not isinstance(values, dict):
return False
for key, val in values.items():
if not isinstance(key, str):
return False
if isinstance(val, str):
continue
if not isinstance(val, list):
return False
for entry in val:
if not isinstance(entry, str):
return False
return True
def is_tuple_list(values: RenderValues) -> TypeGuard[TupleList]:
if not isinstance(values, list):
return False
for item in values:
if not isinstance(item, tuple):
return False
if not len(item) == 2:
return False
if not isinstance(item[0], str):
return False
if not isinstance(item[1], str):
return False
return True
def render_values_to_list_dict(values: RenderValues) -> ListDict:
def __tuple_list_to_dict(src: TupleList) -> ListDict:
ret: ListDict = {}
for key, val in src:
entry = ret.setdefault(key, [])
entry.append(val)
return ret
if is_list_dict(values):
return values
if is_tuple_list(values):
return __tuple_list_to_dict(values)
raise Exception('Unsupported template value layout')
def merge_values(*values: RenderValues) -> ListDict:
ret: ListDict = {}
for rhs in values:
rhs_dict = render_values_to_list_dict(rhs)
for key, val in rhs_dict.items():
entry = ret.setdefault(key, [])
if isinstance(val, list):
entry += val
else:
entry.append(val)
return ret
def format_list_dict(
template: str, values: ListDict | dict[str, str], li_quote: bool, li_delimiter: str
) -> str: ) -> str:
def __format_value(val): def __format_value(val):
@ -9,15 +81,18 @@ def format_lines(
return str(val) return str(val)
return f'"{val}"' return f'"{val}"'
fmt_dict: dict[str, str] = {}
for key, value in values.items(): for key, value in values.items():
if isinstance(value, (list, tuple)): if isinstance(value, (list, tuple)):
values[key] = li_delimiter.join(map(__format_value, value)) fmt_dict[key] = li_delimiter.join(map(__format_value, value))
elif isinstance(value, str):
fmt_dict[key] = value
ret = [] ret: list[str] = []
parts = template.splitlines(keepends = True) parts = template.splitlines(keepends = True)
for line in parts: for line in parts:
for key, value in values.items(): for key, value in fmt_dict.items():
marker = '{' + key + '}' marker = '{' + key + '}'
if marker in line: if marker in line:
indent = line[:line.index(marker)] indent = line[:line.index(marker)]
@ -27,6 +102,11 @@ def format_lines(
return ''.join(ret) return ''.join(ret)
def format_lines(
template: str, values: list[RenderValues], li_quote: bool, li_delimiter: str
) -> str:
return format_list_dict(template, merge_values(*values), li_quote, li_delimiter)
_templates = { _templates = {
'pkg-config': 'pkg-config':
"""\ """\
@ -66,7 +146,7 @@ _templates = {
def tmpl_render( def tmpl_render(
template_name: str, template_name: str,
values, values: list[RenderValues],
li_quote = False, li_quote = False,
li_delimiter = '\n', li_delimiter = '\n',
search_path: list[str] = [] search_path: list[str] = []