From 49016373e12d3d3554aaa4ee1c93f1faec1bc0d3 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Wed, 27 May 2026 15:46:00 +0200 Subject: [PATCH] cmds.projects.CmdCreateFile: Add module Add CmdCreateFile as a command to generate files from project metadata. It uses the new tmpl_render() engine, might serve as a central location to replace other code generating files, let's see how that evolves. Signed-off-by: Jan Lindemann --- .../jw/pkg/cmds/projects/CmdCreateFile.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/python/jw/pkg/cmds/projects/CmdCreateFile.py diff --git a/src/python/jw/pkg/cmds/projects/CmdCreateFile.py b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py new file mode 100644 index 00000000..5d4e5ec6 --- /dev/null +++ b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py @@ -0,0 +1,102 @@ +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 +from .lib.templates import tmpl_render + +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 + + 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: + path = self.app.find_dir(m) + if path is None: + log(WARNING, f'No project directory for module "{m}"') + continue + extra_paths.append(path) + paths = ',\n'.join([f'"{path}"' for path in extra_paths]) + values: ListDict = {} + kwargs = { + 'extra_paths': paths, + } + self.__update_dict(values, extra_fields) + self.__update_dict(values, kwargs.items()) + + kwargs.update(extra_fields) + return tmpl_render( + 'pyrightconfig.json', values, li_quote = True, li_delimiter = ',\n' + ) + + 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' + ) + 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))