From 7e2ac4701809cee0087dab0e8fd83ab802fd3684 Mon Sep 17 00:00:00 2001 From: Ondrej Nosek Date: Oct 07 2021 11:56:33 +0000 Subject: Support for custom completers Argcomplete library supports custom completers, but their assigning to arguments should be done before rpkg initialization. This commit introduces mechanism how to pass specific completers for rhpkg/fedpkg to rpkg. JIRA: RHELCMP-89 Signed-off-by: Dominik Rumian --- diff --git a/jenkins_test.dockerfile b/jenkins_test.dockerfile index 403cbde..3c40da4 100644 --- a/jenkins_test.dockerfile +++ b/jenkins_test.dockerfile @@ -1,4 +1,4 @@ -FROM fedora:33 +FROM fedora:34 LABEL \ name="rpkg test" \ description="Run tests using tox with Python 3" \ diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py index fafada6..decf29d 100644 --- a/pyrpkg/cli.py +++ b/pyrpkg/cli.py @@ -30,6 +30,7 @@ import six from requests.auth import HTTPBasicAuth from six.moves import configparser +import pyrpkg.completers as completers import pyrpkg.utils as utils from pyrpkg import Modulemd, rpkgError @@ -132,6 +133,11 @@ else: class cliClient(object): """This is a client class for rpkg clients.""" + # Structure contains completer methods. These are set from children + # classes in fedpkg/rhpkg before __init__ is run here. + # Methods are assigned to specific argument in register_xxx methods. + _completers = {} + def __init__(self, config, name=None): """This requires a ConfigParser object @@ -302,6 +308,31 @@ class cliClient(object): except ImportError: raise Exception('Unknown site %s' % site) + @staticmethod + def get_completer(name): + """Returns one custom completer from the '_completers' structure.""" + res = cliClient._completers.get(name, None) # get CustomCompleterWrapper object + if res is not None: + if res.__class__.__name__ == "CustomCompleterWrapper": + return res.fetch_completer() + else: + return res + + @staticmethod + def set_completer(name, method, *args, **kwargs): + """Initializes custom completer and stores it into the '_completers' structure. + 'name' is a search key in the '_completers' structure. + 'method' is function that defines completer's values/choices. + Additional arguments are later passed to the 'method'""" + if args: + cliClient._completers[name] = completers.CustomCompleterWrapper(method, *args, *kwargs) + else: + cliClient._completers[name] = method + + def setup_completers(self): + """Prepares custom completers.""" + cliClient.set_completer("distgit_namespaces", completers.distgit_namespaces, self) + def setup_argparser(self): """Setup the argument parser and register some basic commands.""" @@ -334,12 +365,13 @@ class cliClient(object): 'namespace. If not specified, name is discovered from Git ' 'push URL or Git URL (last part of path with .git extension ' 'removed) or from Name macro in spec file, in that order.') - self.parser.add_argument( + distgit_namespaces = self.parser.add_argument( '--namespace', metavar='NAMESPACE', dest='repo_namespace', help='The package repository namespace. If omitted, default to ' 'rpms if namespace is enabled.') + distgit_namespaces.completer = self.get_completer("distgit_namespaces") # Override the discovered user name self.parser.add_argument('--user', default=None, help='Override the discovered user name') @@ -444,17 +476,19 @@ class cliClient(object): self.build_parser_common = ArgumentParser( 'build_common', add_help=False, allow_abbrev=False) - self.build_parser_common.add_argument( + build_arches = self.build_parser_common.add_argument( '--arches', nargs='*', help='Build for specific arches') + build_arches.completer = cliClient.get_completer("build_arches") self.build_parser_common.add_argument( '--md5', action='store_const', const='md5', default=None, dest='hash', help='Use md5 checksums (for older rpm hosts)') self.build_parser_common.add_argument( '--nowait', action='store_true', default=False, help="Don't wait on build") - self.build_parser_common.add_argument( + list_targets = self.build_parser_common.add_argument( '--target', default=None, help='Define build target to build into') + list_targets.completer = cliClient.get_completer("list_targets") self.build_parser_common.add_argument( '--background', action='store_true', default=False, help='Run the build at a low priority') @@ -482,8 +516,9 @@ class cliClient(object): self.rpm_parser_common.add_argument( '--buildrootdir', default=None, help='Define an alternate buildrootdir') - self.rpm_parser_common.add_argument( + rpm_arches = self.rpm_parser_common.add_argument( '--arch', help='Prep for a specific arch') + rpm_arches.completer = cliClient.get_completer("build_arches") self.rpm_parser_common.add_argument( '--define', help='Pass custom macros to rpmbuild, may specify multiple times', action='append') @@ -539,9 +574,10 @@ class cliClient(object): by libgizmo and then the current directory package. If no groups are defined, packages will be built sequentially. """ % {'name': self.name})) - chainbuild_parser.add_argument( + packages = chainbuild_parser.add_argument( 'package', nargs='+', help='List the packages and order you want to build in') + packages.completer = cliClient.get_completer("packages") chainbuild_parser.set_defaults(command=self.chainbuild) def register_clean(self): @@ -587,8 +623,9 @@ class cliClient(object): '--branches', '-B', action='store_true', help='Do an old style checkout with subdirs for branches') # provide a convenient way to get to a specific branch - clone_parser.add_argument( + branches = clone_parser.add_argument( '--branch', '-b', help='Check out a specific branch') + branches.completer = cliClient.get_completer("branches") # allow to clone without needing a account on the scm server clone_parser.add_argument( '--anonymous', '-a', action='store_true', @@ -601,7 +638,7 @@ class cliClient(object): raise argparse.ArgumentTypeError("argument can't contain an URL") return raw_value # store the module to be cloned - clone_parser.add_argument( + packages = clone_parser.add_argument( 'repo', nargs=1, type=validator_not_url, help="Name of the repository to clone. " "It should not be a Git URL. " @@ -609,6 +646,7 @@ class cliClient(object): "Otherwise, just 'repo-name'. " "Namespace examples are 'rpms', 'container', 'modules', 'flatpaks'. " "Default namespace 'rpms' can be ignored. ") + packages.completer = cliClient.get_completer("packages") # Eventually specify where to clone the module clone_parser.add_argument( "clone_target", default=None, nargs="?", @@ -1089,9 +1127,12 @@ class cliClient(object): 'mock-config', help='Generate a mock config', description='This will generate a mock config based on the ' 'buildsystem target') - mock_config_parser.add_argument( + mock_list_targets = mock_config_parser.add_argument( '--target', help='Override target used for config', default=None) - mock_config_parser.add_argument('--arch', help='Override local arch') + mock_list_targets.completer = cliClient.get_completer("list_targets") + mock_arches = mock_config_parser.add_argument('--arch', + help='Override local arch') + mock_arches.completer = cliClient.get_completer("build_arches") mock_config_parser.set_defaults(command=self.mock_config) def register_module_build_common(self): @@ -1465,8 +1506,10 @@ class cliClient(object): 'have a local match it will create one. It can also ' 'be used to list the existing local and remote ' 'branches.') - switch_branch_parser.add_argument( + branches = switch_branch_parser.add_argument( 'branch', nargs='?', help='Branch name to switch to') + # TODO: Solve the issue with listing also other arguments + branches.completer = cliClient.get_completer("branches") switch_branch_parser.add_argument( '-l', '--list', action='store_true', help='List both remote-tracking branches and local branches') @@ -1554,10 +1597,11 @@ class cliClient(object): self.container_build_parser_common = parser - parser.add_argument( + container_list_targets = parser.add_argument( '--target', help='Override the default target', default=None) + container_list_targets.completer = cliClient.get_completer("list_targets") parser.add_argument( '--nowait', @@ -1587,11 +1631,12 @@ class cliClient(object): help='Scratch build', action="store_true") - parser.add_argument( + container_build_arches = parser.add_argument( '--arches', action='append', nargs='*', help='Limit a scratch or a isolated build to an arch. May have multiple arches.') + container_build_arches.completer = cliClient.get_completer("build_arches") parser.add_argument( '--repo-url', diff --git a/pyrpkg/completers.py b/pyrpkg/completers.py new file mode 100644 index 0000000..0d11b35 --- /dev/null +++ b/pyrpkg/completers.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# completers.py - custom argument completers module for fedpkg +# +# Copyright (C) 2019 Red Hat Inc. +# Author(s): Ondrej Nosek , +# Dominik Rumian +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. See http://www.gnu.org/copyleft/gpl.html for +# the full text of the license. + +from argcomplete.completers import ChoicesCompleter + + +class CustomCompleterWrapper(object): + """ + Class that allows passing additional arguments to custom completer methods. + Completer methods should provide ChoicesCompleter (or other similar object + from 'argcomplete.completers' class) as their results. + """ + def __init__(self, method, *args, **kwargs): + self.method = method + self.args = args + self.kwargs = kwargs + + def fetch_completer(self): + return self.method(*self.args, **self.kwargs) + + +def distgit_namespaces(cli): + if cli.config.has_option(cli.name, 'distgit_namespaces'): + return ChoicesCompleter(cli.config.get(cli.name, 'distgit_namespaces').split()) + else: + return None diff --git a/requirements/pypi.txt b/requirements/pypi.txt index 5f2fd2c..634e966 100644 --- a/requirements/pypi.txt +++ b/requirements/pypi.txt @@ -7,6 +7,7 @@ pycurl >= 7.19 six >= 1.9.0 requests PyYAML +argcomplete # rpm-py-installer # diff --git a/requirements/test-pypi.txt b/requirements/test-pypi.txt index 36e3436..b732dcb 100644 --- a/requirements/test-pypi.txt +++ b/requirements/test-pypi.txt @@ -3,6 +3,7 @@ mock == 1.0.1 flake8 pytest pytest-cov +argcomplete # used in MBS tests openidc-client