From 1349665c39dc30c98d00d1f70fc19267c7e39f28 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Sat, 12 Sep 2026 19:33:54 +0200 Subject: [PATCH] cmds.projects.CmdCreateFile: Add --field-separator option List values in a rendered template are joined with a fixed separator of ",\n" (comma plus newline), and there is no way to change that. Templates sometimes want a different separator, e.g. a plain newline or a single-line comma-separated list. Add a --field-separator option that controls how list values are joined in the rendered output. %n expands to a newline, and the default remains ",\n" for both formats. render_tmpl() applies the separator to the rendered template, while render_pyright() rejects any other value: the built-in pyrightconfig.json template is a fixed JSON document, and no alternative separator is supported for it. Update the help output test expectation accordingly. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.85.1 Signed-off-by: Jan Lindemann --- .../jw/pkg/cmds/projects/CmdCreateFile.py | 47 +++++++++++++++---- .../integration/jw-pkg/help/test-expected.txt | 4 ++ .../pkg/cmds/projects/CmdCreateFile/Makefile | 8 ++++ .../pkg/cmds/projects/CmdCreateFile/test.py | 25 ++++++++++ 4 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/Makefile create mode 100644 test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/test.py diff --git a/src/python/jw/pkg/cmds/projects/CmdCreateFile.py b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py index 007d9734..f1d09a17 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCreateFile.py +++ b/src/python/jw/pkg/cmds/projects/CmdCreateFile.py @@ -55,32 +55,56 @@ class CmdCreateFile(Cmd): # export p = 'render_' return [name.removeprefix(p) for name in dir(self) if name.startswith(p)] + def __format_separator(self, separator: str | None, default: str) -> str: + if separator is None: + return default + format_chars = { + '%n': '\n', + } + for src, dst in format_chars.items(): + separator = separator.replace(src, dst) + return separator + def __render( self, template_name: str, values: list[RenderValues], li_quote: bool = False, - li_delimiter: str = '\n', + li_separator: str = '\n', ) -> str: return tmpl_render( template_name, values, li_quote = li_quote, - li_delimiter = li_delimiter, + li_delimiter = li_separator, search_path = self.app.args.search_path.split(':'), ) - def render_tmpl(self, module: str, extra_fields: RenderValues) -> str: + def render_tmpl( + self, + module: str, + extra_fields: RenderValues, + separator: str | None, + ) -> str: template_name = self.app.args.template_name if template_name is None: raise Exception('Can\'t render template without name') return self.__render( - template_name, [extra_fields], + template_name, + [extra_fields], li_quote = self.app.args.quote, - li_delimiter = ',\n' + li_separator = self.__format_separator(separator, ',\n'), ) - def render_pyright(self, module: str, extra_fields: RenderValues) -> str: + def render_pyright( + self, + module: str, + extra_fields: RenderValues, + separator: str | None, + ) -> str: + separator = self.__format_separator(separator, ',\n') + if separator != ',\n': + raise Exception(f'Unsupported separator for pyright config: "{separator}"') extra_paths = [] for m in self.__jw_required(include_self = True): path = self.app.find_dir(m, search_subdirs = ['src/python', 'tools/python']) @@ -94,7 +118,7 @@ class CmdCreateFile(Cmd): # export return self.__render( 'pyrightconfig.json', [values, extra_fields], li_quote = True, - li_delimiter = ',\n' + li_separator = ',\n' ) def __init__(self, parent: Parent) -> None: @@ -142,6 +166,13 @@ class CmdCreateFile(Cmd): # export 'rendering output' ) ) + parser.add_argument( + '--field-separator', + help = ( + 'Field separator. %%n expands to newline. Default value depends on ' + '--format' + ) + ) parser.add_argument('module', help = 'The module to generate the file for') @override @@ -166,4 +197,4 @@ class CmdCreateFile(Cmd): # export raise Exception(f'Unsupported output format {args.format}') # -- Render - sys.stdout.write(method(args.module, fields)) + sys.stdout.write(method(args.module, fields, args.field_separator)) diff --git a/test/integration/jw-pkg/help/test-expected.txt b/test/integration/jw-pkg/help/test-expected.txt index ae035045..5bd788a1 100644 --- a/test/integration/jw-pkg/help/test-expected.txt +++ b/test/integration/jw-pkg/help/test-expected.txt @@ -337,6 +337,7 @@ usage: jw-pkg.py projects create-file [-h] --format {pyright,tmpl} [--template-name TEMPLATE_NAME] [--quote] [-f KEY=VALUE] [--field-keys FIELD_KEYS] + [--field-separator FIELD_SEPARATOR] module Generate a file from project metadata @@ -365,6 +366,9 @@ options: not specified here is an error, and not passing one makes the respective field go away from the rendering output (default: None) + --field-separator FIELD_SEPARATOR + Field separator. %n expands to newline. Default value + depends on --format (default: None) ============= Running: jw-pkg.py -t ../../../.. --log-level info projects create-pkg-config --help usage: jw-pkg.py projects create-pkg-config [-h] [-F PROJECT_DESCR_FILE] [-d DESCRIPTION] [-n NAME] diff --git a/test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/Makefile b/test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/Makefile new file mode 100644 index 00000000..47708b2e --- /dev/null +++ b/test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/Makefile @@ -0,0 +1,8 @@ +TOPDIR = ../../../../../../../.. + +include $(TOPDIR)/make/proj.mk +include $(JWBDIR)/make/py-run.mk + +all: + +test: run diff --git a/test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/test.py b/test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/test.py new file mode 100644 index 00000000..c5000bd0 --- /dev/null +++ b/test/unit/python/jw/pkg/cmds/projects/CmdCreateFile/test.py @@ -0,0 +1,25 @@ +from jw.pkg.cmds.projects.CmdCreateFile import CmdCreateFile + +# __format_separator() is a private method that does not use self, so an +# uninitialised instance is a sufficient receiver for the bound method +inst = CmdCreateFile.__new__(CmdCreateFile) +fmt = getattr(inst, '_CmdCreateFile__format_separator') + +# -- __format_separator -- + +# A None separator falls back to the format's default +assert fmt(None, ',\n') == ',\n' + +# %n expands to a newline, wherever it appears in the separator +assert fmt('%n', ',\n') == '\n' +assert fmt(',%n', '\n') == ',\n' +assert fmt('a%n b', '') == 'a\n b' + +# Several %n expand independently +assert fmt('%n%n', '') == '\n\n' + +# Anything that is not %n passes through untouched +assert fmt('%', '') == '%' +assert fmt('%x', '') == '%x' + +print('All CmdCreateFile tests passed')