From 362bd6ae7ca27fecf7ddc2c0f2ca1ee11ea2c571 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 14 Apr 2026 19:04:55 +0200 Subject: [PATCH] cli: accept --json/-v/-q on every subcommand, not only top level Before this change, only the top-level parser defined --json, -v and -q. Argparse processes arguments left-to-right and hands off to the subparser after seeing the subcommand name, so the idiomatic metin-release release inspect --source X --json failed with 'unrecognized arguments: --json'. Users had to write metin-release --json release inspect --source X which is the opposite of what every modern CLI does. Attach a shared --json/-v/-q flag set to every subparser via a small helper. Same dest means the last occurrence on the command line wins, which is the intuitive behaviour. Both placements are now accepted; tests unchanged. --- src/metin_release/cli.py | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/metin_release/cli.py b/src/metin_release/cli.py index c1d1c38..9b85ae5 100644 --- a/src/metin_release/cli.py +++ b/src/metin_release/cli.py @@ -26,15 +26,27 @@ from .workspace import Context CommandFn = Callable[[Context, argparse.Namespace], Result] +def _add_common_flags(p: argparse.ArgumentParser) -> None: + """Attach --json / -v / -q to a parser. + + These flags are accepted both at the top level and on each subcommand so + that users can write either `metin-release --json release inspect ...` or + the more common `metin-release release inspect ... --json`. The shared + `dest` means whichever occurrence argparse sees last wins, which is the + intuitive "last flag on the command line" behaviour. + """ + p.add_argument("--json", action="store_true", help="Emit only JSON on stdout.") + p.add_argument("-v", "--verbose", action="store_true", help="Verbose stderr logging.") + p.add_argument("-q", "--quiet", action="store_true", help="Suppress stderr logging.") + + def _build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="metin-release", description="Orchestration CLI for Metin2 client releases.", ) parser.add_argument("--version", action="version", version=f"metin-release {__version__}") - parser.add_argument("--json", action="store_true", help="Emit only JSON on stdout.") - parser.add_argument("-v", "--verbose", action="store_true", help="Verbose stderr logging.") - parser.add_argument("-q", "--quiet", action="store_true", help="Suppress stderr logging.") + _add_common_flags(parser) sub = parser.add_subparsers(dest="group", metavar="") sub.required = True @@ -43,14 +55,18 @@ def _build_parser() -> argparse.ArgumentParser: rsub = release.add_subparsers(dest="cmd", metavar="") rsub.required = True - inspect.add_parser(rsub) - build_manifest.add_parser(rsub) - sign.add_parser(rsub) - diff_remote.add_parser(rsub) - upload_blobs.add_parser(rsub) - promote.add_parser(rsub) - verify_public.add_parser(rsub) - publish.add_parser(rsub) + for mod in ( + inspect, + build_manifest, + sign, + diff_remote, + upload_blobs, + promote, + verify_public, + publish, + ): + sp = mod.add_parser(rsub) + _add_common_flags(sp) return parser