2026-05-27 15:46:00 +02:00
|
|
|
from argparse import ArgumentParser, ArgumentTypeError, Namespace
|
|
|
|
|
from enum import Enum, auto
|
|
|
|
|
from typing import Iterable, TypeAlias
|
|
|
|
|
|
|
|
|
|
from ...lib.log import WARNING, log
|
|
|
|
|
from .Cmd import Cmd, Parent
|
|
|
|
|
from .lib.pkg_relations import VersionSyntax, pkg_relations
|
2026-06-02 21:18:21 +02:00
|
|
|
from .lib.templates import RenderValues, tmpl_render
|
2026-05-27 15:46:00 +02:00
|
|
|
|
|
|
|
|
def key_value(s):
|
|
|
|
|
try:
|
|
|
|
|
key, value = s.split('=', 1)
|
|
|
|
|
except ValueError:
|
|
|
|
|
raise ArgumentTypeError('Expected KEY=VALUE')
|
|
|
|
|
return key, value
|
|
|
|
|
|
|
|
|
|
# TODO: Put the more elaborate stuff into lib
|
|
|
|
|
class Fmt(Enum):
|
|
|
|
|
Pyright = auto()
|
|
|
|
|
|
|
|
|
|
TupleList: TypeAlias = Iterable[tuple[str, str]]
|
|
|
|
|
ListDict: TypeAlias = dict[str, list[str]]
|
|
|
|
|
|
|
|
|
|
class CmdCreateFile(Cmd): # export
|
|
|
|
|
|
|
|
|
|
def __tuple_list_to_dict(self, src: TupleList) -> ListDict:
|
|
|
|
|
ret: ListDict = {}
|
|
|
|
|
for key, val in src:
|
|
|
|
|
entry = ret.setdefault(key, [])
|
|
|
|
|
entry.append(val)
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
def __update_dict(self, dst: ListDict, src: TupleList) -> ListDict:
|
|
|
|
|
ret = dst
|
|
|
|
|
for key, val in src:
|
|
|
|
|
entry = ret.setdefault(key, [])
|
|
|
|
|
entry.append(val)
|
|
|
|
|
return ret
|
|
|
|
|
|
2026-06-02 21:18:21 +02:00
|
|
|
def __render(
|
|
|
|
|
self,
|
|
|
|
|
template_name: str,
|
|
|
|
|
values: list[RenderValues],
|
|
|
|
|
li_quote = False,
|
|
|
|
|
li_delimiter = '\n',
|
|
|
|
|
) -> str:
|
|
|
|
|
return tmpl_render(
|
|
|
|
|
template_name,
|
|
|
|
|
values,
|
|
|
|
|
li_quote = li_quote,
|
|
|
|
|
li_delimiter = li_delimiter,
|
|
|
|
|
search_path = self.app.args.search_path.split(':'),
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-27 15:46:00 +02:00
|
|
|
def render_pyright(self, module: str, extra_fields: TupleList) -> str:
|
|
|
|
|
prereq = pkg_relations(
|
|
|
|
|
self.app,
|
|
|
|
|
rel_type = 'requires',
|
|
|
|
|
flavours = ['run'],
|
|
|
|
|
subsections = ['jw'],
|
|
|
|
|
seed_pkgs = [module],
|
|
|
|
|
syntax = VersionSyntax.names_only,
|
|
|
|
|
no_subpackages = True,
|
|
|
|
|
recursive = True,
|
|
|
|
|
quote = False,
|
|
|
|
|
hide_self = False,
|
|
|
|
|
hide_jw_pkg = False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
extra_paths = []
|
|
|
|
|
for m in prereq:
|
2026-06-05 15:02:56 +02:00
|
|
|
path = self.app.find_dir(m, search_subdirs = ['src/python', 'tools/python'])
|
2026-05-27 15:46:00 +02:00
|
|
|
if path is None:
|
|
|
|
|
log(WARNING, f'No project directory for module "{m}"')
|
|
|
|
|
continue
|
|
|
|
|
extra_paths.append(path)
|
2026-06-02 20:20:27 +02:00
|
|
|
values: ListDict = {
|
|
|
|
|
'extra_paths': extra_paths,
|
2026-05-27 15:46:00 +02:00
|
|
|
}
|
|
|
|
|
self.__update_dict(values, extra_fields)
|
|
|
|
|
|
2026-06-02 21:18:21 +02:00
|
|
|
return self.__render(
|
2026-06-04 07:27:52 +02:00
|
|
|
'pyrightconfig.json', [values], li_quote = True, li_delimiter = ',\n'
|
2026-05-27 15:46:00 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent: Parent) -> None:
|
|
|
|
|
super().__init__(
|
|
|
|
|
parent, 'create-file', help = 'Generate a file from project metadata'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def add_arguments(self, parser: ArgumentParser) -> None:
|
|
|
|
|
super().add_arguments(parser)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'--format',
|
|
|
|
|
choices = [fmt.name.lower() for fmt in Fmt],
|
|
|
|
|
help = 'Output format'
|
|
|
|
|
)
|
2026-06-02 21:18:21 +02:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--search-path',
|
|
|
|
|
default = '/etc/opt/jw-pkg/templates',
|
|
|
|
|
help = 'Template search path, colon separated',
|
|
|
|
|
)
|
2026-05-27 15:46:00 +02:00
|
|
|
parser.add_argument(
|
|
|
|
|
'-f',
|
|
|
|
|
'--field',
|
|
|
|
|
action = 'append',
|
|
|
|
|
type = key_value,
|
|
|
|
|
default = [],
|
|
|
|
|
metavar = 'KEY=VALUE',
|
|
|
|
|
help = 'Additional fields to insert into the output file',
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument('module', help = 'The module to generate the file for')
|
|
|
|
|
|
|
|
|
|
async def _run(self, args: Namespace) -> None:
|
|
|
|
|
method = getattr(self, 'render_' + args.format, None)
|
|
|
|
|
if method is None: # Should be prevented by choices=[] but keeps linter happy
|
|
|
|
|
raise Exception(f'Unsupported output format {args.format}')
|
|
|
|
|
print(method(args.module, args.field))
|