Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tmt about, command showing things about tmt itself #3470

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tmt/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def import_cli_commands() -> None:

# TODO: some kind of `import tmt.cli.*` would be nice
import tmt.cli._root # type: ignore[reportUnusedImport,unused-ignore]
import tmt.cli.about # noqa: F401,I001,RUF100
import tmt.cli.init # noqa: F401,I001,RUF100
import tmt.cli.lint # noqa: F401,I001,RUF100
import tmt.cli.status # noqa: F401,I001,RUF100
Expand Down
2 changes: 1 addition & 1 deletion tmt/checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

CheckPluginClass = type['CheckPlugin[Any]']

_CHECK_PLUGIN_REGISTRY: PluginRegistry[CheckPluginClass] = PluginRegistry()
_CHECK_PLUGIN_REGISTRY: PluginRegistry[CheckPluginClass] = PluginRegistry('test.check')


def provides_check(check: str) -> Callable[[CheckPluginClass], CheckPluginClass]:
Expand Down
89 changes: 89 additions & 0 deletions tmt/cli/about.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
""" ``tmt about`` implementation """

import json
import re
import textwrap
from typing import Any

import tmt.utils
import tmt.utils.rest
from tmt.cli import Context, CustomGroup, pass_context
from tmt.cli._root import main
from tmt.options import option
from tmt.plugins import PluginRegistry, iter_plugin_registries
from tmt.utils import GeneralError
from tmt.utils.templates import render_template


@main.group(invoke_without_command=True, cls=CustomGroup)
def about() -> None:
""" Show info about tmt itself """


def _render_plugins_rest() -> str:
registry_intro_map: dict[str, str] = {
r'export\.([a-z]+)': 'Export plugins for {{ MATCH.group(1).lower() }}',
r'test.check': 'Test check plugins',
r'test.framework': 'Test framework plugins',
r'package_manager': 'Package manager plugins',
r'step\.([a-z]+)': '{{ MATCH.group(1).capitalize() }} step plugins'
}

def find_intro(registry: PluginRegistry[Any]) -> str:
for pattern, intro_template in registry_intro_map.items():
match = re.match(pattern, registry.name)

if match is None:
continue

return render_template(intro_template, MATCH=match)

raise GeneralError(f"Unknown plugin registry '{registry.name}'.")

template = textwrap.dedent("""
{% for registry in REGISTRIES %}
{% set intro = find_intro(registry) %}
{{ intro }}
{# {{ "-" * intro | length }} #}

{% if registry %}
{% for plugin_id in registry.iter_plugin_ids() %}
* `{{ plugin_id }}`
{% endfor %}
{% else %}
No plugins discovered.
{% endif %}

----

{% endfor %}
""")

return render_template(template, REGISTRIES=iter_plugin_registries(), find_intro=find_intro)


@about.command()
@option(
'-h', '--how',
choices=['json', 'yaml', 'rest', 'pretty'],
default='pretty',
help='Output format.')
@pass_context
def plugins(context: Context, how: str) -> None:
print = context.obj.logger.print # noqa: A001

if how in ('pretty', 'rest'):
text_output = _render_plugins_rest()

print(
tmt.utils.rest.render_rst(text_output, context.obj.logger)
if how == 'pretty' else text_output)

elif how in ('json', 'yaml'):
structured_output = {
registry.name: list(registry.iter_plugin_ids())
for registry in iter_plugin_registries()
}

print(json.dumps(structured_output) if how ==
'json' else tmt.utils.dict_to_yaml(structured_output))
2 changes: 1 addition & 1 deletion tmt/export/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def get_export_plugin_registry(cls) -> PluginRegistry[ExportClass]:
""" Return - or initialize - export plugin registry """

if not hasattr(cls, '_export_plugin_registry'):
cls._export_plugin_registry = PluginRegistry()
cls._export_plugin_registry = PluginRegistry(f'export.{cls.__name__.lower()}')

return cls._export_plugin_registry

Expand Down
2 changes: 1 addition & 1 deletion tmt/frameworks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


_FRAMEWORK_PLUGIN_REGISTRY: tmt.plugins.PluginRegistry[TestFrameworkClass] = \
tmt.plugins.PluginRegistry()
tmt.plugins.PluginRegistry('test.framework')


def provides_framework(framework: str) -> Callable[[TestFrameworkClass], TestFrameworkClass]:
Expand Down
2 changes: 1 addition & 1 deletion tmt/package_managers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def __lt__(self, other: Any) -> bool:


_PACKAGE_MANAGER_PLUGIN_REGISTRY: tmt.plugins.PluginRegistry[PackageManagerClass] = \
tmt.plugins.PluginRegistry()
tmt.plugins.PluginRegistry('package_managers')


def provides_package_manager(
Expand Down
45 changes: 44 additions & 1 deletion tmt/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,9 @@ class PluginRegistry(Generic[RegisterableT]):

_plugins: dict[str, RegisterableT]

def __init__(self) -> None:
def __init__(self, name: str) -> None:
self.name = name

self._plugins = {}

def register_plugin(
Expand Down Expand Up @@ -370,6 +372,12 @@ def iter_plugins(self) -> Iterator[RegisterableT]:
def items(self) -> Iterator[tuple[str, RegisterableT]]:
yield from self._plugins.items()

def __len__(self) -> int:
return len(self._plugins)

def __bool__(self) -> bool:
return bool(self._plugins)


class ModuleImporter(Generic[ModuleT]):
"""
Expand Down Expand Up @@ -402,3 +410,38 @@ def __call__(self, logger: Logger) -> ModuleT:

assert self._module # narrow type
return self._module


def iter_plugin_registries() -> Iterator[PluginRegistry[Any]]:
# TODO: maybe there is a better way, but as of now, registries do
# not report their existence, there is no method to iterate over
# them. Using a static list for now.
from tmt.base import Plan, Story, Test
from tmt.checks import _CHECK_PLUGIN_REGISTRY
from tmt.frameworks import _FRAMEWORK_PLUGIN_REGISTRY
from tmt.package_managers import _PACKAGE_MANAGER_PLUGIN_REGISTRY
from tmt.steps.discover import DiscoverPlugin
from tmt.steps.execute import ExecutePlugin
from tmt.steps.finish import FinishPlugin
from tmt.steps.prepare import PreparePlugin
from tmt.steps.provision import ProvisionPlugin
from tmt.steps.report import ReportPlugin

yield Story._export_plugin_registry
yield Plan._export_plugin_registry
yield Test._export_plugin_registry
yield _CHECK_PLUGIN_REGISTRY
yield _FRAMEWORK_PLUGIN_REGISTRY
yield _PACKAGE_MANAGER_PLUGIN_REGISTRY
yield DiscoverPlugin._supported_methods
yield ProvisionPlugin._supported_methods
yield PreparePlugin._supported_methods
yield ExecutePlugin._supported_methods
yield FinishPlugin._supported_methods
yield ReportPlugin._supported_methods


def iter_plugins() -> Iterator[tuple[PluginRegistry[RegisterableT], str, RegisterableT]]:
for registry in iter_plugin_registries():
for plugin_id, plugin_class in registry.items():
yield registry, plugin_id, plugin_class
2 changes: 1 addition & 1 deletion tmt/steps/discover/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class DiscoverPlugin(tmt.steps.GuestlessPlugin[DiscoverStepDataT, None]):
_data_class = DiscoverStepData # type: ignore[assignment]

# Methods ("how: ..." implementations) registered for the same step.
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry()
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry('step.discover')

@classmethod
def base_command(
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/execute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ class ExecutePlugin(tmt.steps.Plugin[ExecuteStepDataT, None]):
_data_class = ExecuteStepData # type: ignore[assignment]

# Methods ("how: ..." implementations) registered for the same step.
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry()
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry('step.execute')

# Internal executor is the default implementation
how = 'tmt'
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/finish/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class FinishPlugin(tmt.steps.Plugin[FinishStepDataT, list[PhaseResult]]):
_data_class = FinishStepData # type: ignore[assignment]

# Methods ("how: ..." implementations) registered for the same step.
_supported_methods: PluginRegistry[Method] = PluginRegistry()
_supported_methods: PluginRegistry[Method] = PluginRegistry('step.finish')

@classmethod
def base_command(
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/prepare/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class PreparePlugin(tmt.steps.Plugin[PrepareStepDataT, list[PhaseResult]]):
_data_class = PrepareStepData # type: ignore[assignment]

# Methods ("how: ..." implementations) registered for the same step.
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry()
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry('step.prepare')

@classmethod
def base_command(
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/prepare/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
FEATURE_PLAYEBOOK_DIRECTORY = tmt.utils.resource_files('steps/prepare/feature')

FeatureClass = type['Feature']
_FEATURE_PLUGIN_REGISTRY: PluginRegistry[FeatureClass] = PluginRegistry()
_FEATURE_PLUGIN_REGISTRY: PluginRegistry[FeatureClass] = PluginRegistry('prepare.feature')


def provides_feature(
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/provision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2285,7 +2285,7 @@ class ProvisionPlugin(tmt.steps.GuestlessPlugin[ProvisionStepDataT, None]):
how = 'virtual'

# Methods ("how: ..." implementations) registered for the same step.
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry()
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry('step.provision')

# TODO: Generics would provide a better type, https://github.com/teemtee/tmt/issues/1437
_guest: Optional[Guest] = None
Expand Down
2 changes: 1 addition & 1 deletion tmt/steps/report/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ReportPlugin(tmt.steps.GuestlessPlugin[ReportStepDataT, None]):
how = 'display'

# Methods ("how: ..." implementations) registered for the same step.
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry()
_supported_methods: PluginRegistry[tmt.steps.Method] = PluginRegistry('step.report')

@classmethod
def base_command(
Expand Down
Loading