jw-pkg/src/python/jw/pkg/cmds/projects/lib/templates.py

172 lines
4.8 KiB
Python
Raw Normal View History

import textwrap
from typing import Any, Iterable, TypeAlias, TypeGuard
TupleList: TypeAlias = Iterable[tuple[str, 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, [])
entry += val
return ret
def format_list_dict(
template: str, values: ListDict | dict[str, str], li_quote: bool, li_delimiter: str
) -> str:
def __format_value(val: Any) -> str:
if not li_quote:
return str(val)
return f'"{val}"'
fmt_dict: dict[str, str] = {}
for key, value in values.items():
if isinstance(value, (list, tuple)):
fmt_dict[key] = li_delimiter.join(map(__format_value, value))
elif isinstance(value, str):
fmt_dict[key] = value
ret: list[str] = []
parts = template.splitlines(keepends = True)
for line in parts:
for key, value in fmt_dict.items():
marker = '{' + key + '}'
if marker in line:
indent = line[:line.index(marker)]
value = str(value).replace('\n', '\n' + indent)
line = line.replace(marker, value)
ret.append(line)
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 = {
'pkg-config':
"""\
prefix = {prefix}
exec_prefix = {{prefix}}
includedir = {{prefix}}/include
libdir = {{exec_prefix}}/lib
Name: {name}
Description: {description}
Version: {version}
""",
'pyrightconfig.json':
"""\
{
"extends": {base},
"include": [
{include}
],
"exclude": [
"**/__pycache__",
"**/.pytest_cache",
"**/.mypy_cache",
"**/.ruff_cache",
"**/.venv",
"**/build",
"**/dist"
],
"extraPaths": [
{extra_paths}
],
"typeCheckingMode": "basic",
"pythonPlatform": "Linux"
}
""",
}
def tmpl_render(
template_name: str,
values: list[RenderValues],
li_quote: bool = False,
li_delimiter: str = '\n',
search_path: list[str] | None = None,
) -> str:
def __format(template: str) -> str:
return format_lines(
template,
values,
li_quote = li_quote,
li_delimiter = li_delimiter,
)
for d in search_path if search_path else []:
path = d + '/' + template_name
try:
with open(path, 'r') as f:
template = f.read()
return __format(template)
except FileNotFoundError:
pass
raw = _templates.get(template_name, None)
if raw is None:
raise Exception(f'Failed to find template "{template_name}"')
return __format(textwrap.dedent(raw))