From 99937100dfbf89bb504c72c9cd55f600a4a13e85 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Thu, 25 Aug 2022 21:30:50 +0200 Subject: [PATCH 1/5] Allow intermixing positional and optional args --- osc/cmdln.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osc/cmdln.py b/osc/cmdln.py index bb346dd6..d73f4b20 100644 --- a/osc/cmdln.py +++ b/osc/cmdln.py @@ -172,9 +172,6 @@ class Cmdln: for option_args, option_kwargs in options: subparser.add_argument(*option_args, **option_kwargs) - # HACK: inject 'args' to all commands so we don't have to decorate all of them - subparser.add_argument('args', nargs='*') - def argparse_error(self, *args, **kwargs): """ Raise an argument parser error. @@ -208,8 +205,10 @@ class Cmdln: self.create_argparser() - self.options = self.argparser.parse_args(argv[1:]) - self.args = getattr(self.options, "args", []) + self.options, self.args = self.argparser.parse_known_args(argv[1:]) + unrecognized = [i for i in self.args if i.startswith("-")] + if unrecognized: + self.argparser.error(f"unrecognized arguments: " + " ".join(unrecognized)) self.post_argparse() From b5491911eac18463cee83675f7513a00e41bf3fc Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Fri, 26 Aug 2022 16:23:48 +0200 Subject: [PATCH 2/5] Add global options to subcommands so they can be specified in any place --- osc/cmdln.py | 9 ++++ osc/commandline.py | 102 ++++++++++++++++++++++++++++++++------------- 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/osc/cmdln.py b/osc/cmdln.py index d73f4b20..5d69264d 100644 --- a/osc/cmdln.py +++ b/osc/cmdln.py @@ -117,6 +117,7 @@ class Cmdln: ) self.pre_argparse() + self.add_global_options(self.argparser) # map command name to `do_*` function that runs the command self.cmd_map = {} @@ -169,6 +170,8 @@ class Cmdln: prog=self.get_subcommand_prog(cmd_name), formatter_class=HelpFormatter ) + # add hidden copy of global options so they can be used in any place + self.add_global_options(subparser, suppress=True) for option_args, option_kwargs in options: subparser.add_argument(*option_args, **option_kwargs) @@ -190,6 +193,12 @@ class Cmdln: """ pass + def add_global_options(self, parser, suppress=False): + """ + Add options to the main argument parser and all subparsers. + """ + pass + def post_argparse(self): """ Hook method executed after `.main()` calls `parse_args()`. diff --git a/osc/commandline.py b/osc/commandline.py index c7b1c264..577d7f5a 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -3,6 +3,7 @@ # and distributed under the terms of the GNU General Public Licence, # either version 2, or version 3 (at your option). +import argparse import importlib.util import inspect import os @@ -69,35 +70,80 @@ class Osc(cmdln.Cmdln): return project.replace(conf.config['project_separator'], ':') return project - def pre_argparse(self): - """Add global options to the parser (options that are not specific to any subcommand)""" + def add_global_options(self, parser, suppress=False): - optparser = self.argparser - optparser.add_argument('--debugger', action='store_true', - help='jump into the debugger before executing anything') - optparser.add_argument('--post-mortem', action='store_true', - help='jump into the debugger in case of errors') - optparser.add_argument('-t', '--traceback', action='store_true', - help='print call trace in case of errors') - optparser.add_argument('-H', '--http-debug', action='store_true', - help='debug HTTP traffic (filters some headers)') - optparser.add_argument('--http-full-debug', action='store_true', - help='debug HTTP traffic (filters no headers)') - optparser.add_argument('-d', '--debug', action='store_true', - help='print info useful for debugging') - optparser.add_argument('-A', '--apiurl', dest='apiurl', - metavar='URL/alias', - help='specify URL to access API server at or an alias') - optparser.add_argument('-c', '--config', dest='conffile', - metavar='FILE', - help='specify alternate configuration file') - optparser.add_argument('--no-keyring', action='store_true', - help='disable usage of desktop keyring system') - verbose_group = optparser.add_mutually_exclusive_group() - verbose_group.add_argument('-v', '--verbose', action='store_true', - help='increase verbosity') - verbose_group.add_argument('-q', '--quiet', action='store_true', - help='be quiet, not verbose') + def _add_parser_arguments_from_data(argument_parser, data): + for kwargs in data: + args = kwargs.pop("names") + if suppress: + kwargs["help"] = argparse.SUPPRESS + kwargs["default"] = argparse.SUPPRESS + argument_parser.add_argument(*args, **kwargs) + + arguments = [] + arguments.append(dict( + names=['--debugger'], + action='store_true', + help='jump into the debugger before executing anything', + )) + arguments.append(dict( + names=['--post-mortem'], + action='store_true', + help='jump into the debugger in case of errors', + )) + arguments.append(dict( + names=['--traceback'], + action='store_true', + help='print call trace in case of errors', + )) + arguments.append(dict( + names=['-H', '--http-debug'], + action='store_true', + help='debug HTTP traffic (filters some headers)', + )) + arguments.append(dict( + names=['--http-full-debug'], + action='store_true', + help='debug HTTP traffic (filters no headers)', + )) + arguments.append(dict( + names=['--debug'], + action='store_true', + help='print info useful for debugging', + )) + arguments.append(dict( + names=['-A', '--apiurl'], + metavar='URL/alias', + help='specify URL to access API server at or an alias', + )) + arguments.append(dict( + names=['--config'], + dest='conffile', + metavar='FILE', + help='specify alternate configuration file', + )) + arguments.append(dict( + names=['--no-keyring'], + action='store_true', + help='disable usage of desktop keyring system', + )) + + _add_parser_arguments_from_data(parser, arguments) + + verbose_group = parser.add_mutually_exclusive_group() + verbose_group_arguments = [] + verbose_group_arguments.append(dict( + names=['-v', '--verbose'], + action='store_true', + help='increase verbosity', + )) + verbose_group_arguments.append(dict( + names=['-q', '--quiet'], + action='store_true', + help='be quiet, not verbose', + )) + + _add_parser_arguments_from_data(verbose_group, verbose_group_arguments) def post_argparse(self): """merge commandline options into the config""" From 98b76d14b65fc4aed0fc00e6ddb4edf23565ef6d Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Fri, 26 Aug 2022 17:00:58 +0200 Subject: [PATCH 3/5] Replace the '-q/--hide-legend' option in 'prjresults' command with global '-q/--quiet' option --- osc/commandline.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osc/commandline.py b/osc/commandline.py index 577d7f5a..0b9520e2 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -5367,8 +5367,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. # WARNING: this function is also called by do_results. You need to set a default there # as well when adding a new option! - @cmdln.option('-q', '--hide-legend', action='store_true', - help='hide the legend') @cmdln.option('-b', '--brief', action='store_true', help='show the result in "pkgname repo arch result"') @cmdln.option('-w', '--watch', action='store_true', @@ -5424,7 +5422,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. print('Please implement support for osc prjresults --watch without --xml.') return 2 - print('\n'.join(get_prj_results(apiurl, project, hide_legend=opts.hide_legend, \ + print('\n'.join(get_prj_results(apiurl, project, hide_legend=opts.quiet, \ csv=opts.csv, status_filter=opts.status_filter, \ name_filter=opts.name_filter, repo=opts.repo, \ arch=opts.arch, vertical=opts.vertical, \ From 3763bff9018976836569ebc41faa9bdfcbc5f3af Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Fri, 26 Aug 2022 17:02:21 +0200 Subject: [PATCH 4/5] Remove the '-A' option from the 'maintainer' command It conflicts with the global '-A/--apiurl' option. Use the long '--all' version from now on. --- osc/commandline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osc/commandline.py b/osc/commandline.py index 0b9520e2..6470afd9 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -8092,7 +8092,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='define the project where this package is primarily developed') @cmdln.option('-a', '--add', metavar='user', help='add a new person for given role ("maintainer" by default)') - @cmdln.option('-A', '--all', action='store_true', + @cmdln.option('--all', action='store_true', help='list all found entries not just the first one') @cmdln.option('-s', '--set-bugowner', metavar='user', help='Set the bugowner to specified person (or group via group: prefix)') From a362edee5e081ff441b59772ebbaf3465993b2d2 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Fri, 26 Aug 2022 17:10:24 +0200 Subject: [PATCH 5/5] Handle conflicting options manually because the mutually exclusive group is buggy --- osc/commandline.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osc/commandline.py b/osc/commandline.py index 6470afd9..e00d6e68 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -81,6 +81,16 @@ class Osc(cmdln.Cmdln): argument_parser.add_argument(*args, **kwargs) arguments = [] + arguments.append(dict( + names=['-v', '--verbose'], + action='store_true', + help='increase verbosity', + )) + arguments.append(dict( + names=['-q', '--quiet'], + action='store_true', + help='be quiet, not verbose', + )) arguments.append(dict( names=['--debugger'], action='store_true', @@ -130,24 +140,14 @@ class Osc(cmdln.Cmdln): _add_parser_arguments_from_data(parser, arguments) - verbose_group = parser.add_mutually_exclusive_group() - verbose_group_arguments = [] - verbose_group_arguments.append(dict( - names=['-v', '--verbose'], - action='store_true', - help='increase verbosity', - )) - verbose_group_arguments.append(dict( - names=['-q', '--quiet'], - action='store_true', - help='be quiet, not verbose', - )) - - _add_parser_arguments_from_data(verbose_group, verbose_group_arguments) - def post_argparse(self): """merge commandline options into the config""" + # handle conflicting options manually because the mutually exclusive group is buggy + # https://github.com/python/cpython/issues/96310 + if self.options.quiet and self.options.verbose: + self.argparse_error("argument -q/--quiet: not allowed with argument -v/--verbose") + # avoid loading config that may trigger prompt for username, password etc. if not self.options.command: # no command specified