Improve Python config file template substitution #8
3 changed files with 103 additions and 20 deletions
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>
commit
81e71bc5c1
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
'prefix': args.prefix,
|
||||||
'name': args.name,
|
'name': args.name,
|
||||||
'description': merged['summary'],
|
'description': merged['summary'] or '',
|
||||||
'version': args.version,
|
'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)
|
||||||
|
|
|
||||||
|
|
@ -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] = []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue