From 0881cd8f7d1d6d9cd1441ccabe011c0cf38540e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Prchl=C3=ADk?= Date: Mon, 13 Jan 2025 10:31:04 +0100 Subject: [PATCH] Extract `tmt * export` from tmt.cli into standalone module --- tmt/__main__.py | 1 + tmt/cli/_root.py | 243 +------------------------------------------ tmt/cli/export.py | 259 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 261 insertions(+), 242 deletions(-) create mode 100644 tmt/cli/export.py diff --git a/tmt/__main__.py b/tmt/__main__.py index 68fc7a8062..f6f23b49fc 100644 --- a/tmt/__main__.py +++ b/tmt/__main__.py @@ -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.export # 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 diff --git a/tmt/cli/_root.py b/tmt/cli/_root.py index 60dedef6de..342533d8d0 100644 --- a/tmt/cli/_root.py +++ b/tmt/cli/_root.py @@ -21,7 +21,6 @@ import tmt.cli import tmt.config import tmt.convert -import tmt.export import tmt.identifier import tmt.log import tmt.options @@ -33,7 +32,7 @@ import tmt.utils.jira import tmt.utils.rest from tmt.cli import CliInvocation, Context, ContextObject, CustomGroup, pass_context -from tmt.options import Deprecated, create_options_decorator, option +from tmt.options import create_options_decorator, option from tmt.utils import Command, Path, effective_workdir_root if TYPE_CHECKING: @@ -582,131 +581,6 @@ def tests_import( tmt.convert.adjust_runtest(path / 'runtest.sh') -_test_export_formats = list(tmt.Test.get_export_plugin_registry().iter_plugin_ids()) -_test_export_default = 'yaml' - - -@tests.command(name='export') -@pass_context -@filtering_options_long -@option( - '-h', '--how', default=_test_export_default, show_default=True, - help='Output format.', - choices=_test_export_formats) -@option( - '--format', default=_test_export_default, show_default=True, - help='Output format.', - deprecated=Deprecated('1.21', hint='use --how instead'), - choices=_test_export_formats) -@option( - '--nitrate', is_flag=True, - help="Export test metadata to Nitrate.", - deprecated=Deprecated('1.21', hint="use '--how nitrate' instead")) -@option( - '--project-id', help='Use specific Polarion project ID.') -@option( - '--link-polarion / --no-link-polarion', default=False, is_flag=True, - help='Add Polarion link to fmf testcase metadata') -@option( - '--bugzilla', is_flag=True, - help=""" - Link Nitrate case to Bugzilla specified in the 'link' attribute with the relation - 'verifies'. - """) -@option( - '--ignore-git-validation', is_flag=True, - help=""" - Ignore unpublished git changes and export to Nitrate. The case might not be able to be - scheduled! - """) -@option( - '--append-summary / --no-append-summary', default=False, is_flag=True, - help=""" - Include test summary in the Nitrate/Polarion test case summary as well. By default, only - the repository name and test name are used. - """) -@option( - '--create', is_flag=True, - help="Create test cases in nitrate if they don't exist.") -@option( - '--general / --no-general', default=False, is_flag=True, - help=""" - Link Nitrate case to component's General plan. Disabled by default. Note that this will - unlink any previously connected general plans. - """) -@option( - '--link-runs / --no-link-runs', default=False, is_flag=True, - help=""" - Link Nitrate case to all open runs of descendant plans of General plan. Disabled by - default. Implies --general option. - """) -@option( - '--fmf-id', is_flag=True, - help='Show fmf identifiers instead of test metadata.') -@option( - '--duplicate / --no-duplicate', default=False, show_default=True, is_flag=True, - help=""" - Allow or prevent creating duplicates in Nitrate by searching for existing test cases with - the same fmf identifier. - """) -@option( - '-n', '--dry', is_flag=True, - help="Run in dry mode. No changes, please.") -@option( - '-d', '--debug', is_flag=True, - help='Provide as much debugging details as possible.') -# TODO: move to `template` export plugin options -@option( - '--template', metavar='PATH', - help="Path to a template to use for rendering the export. Used with '--how=template' only." - ) -def tests_export( - context: Context, - format: str, - how: str, - nitrate: bool, - bugzilla: bool, - template: Optional[str], - **kwargs: Any) -> None: - """ - Export test data into the desired format. - - Regular expression can be used to filter tests by name. - Use '.' to select tests under the current working directory. - """ - tmt.Test.store_cli_invocation(context) - - if nitrate: - context.obj.common.warn( - "Option '--nitrate' is deprecated, please use '--how nitrate' instead.") - how = 'nitrate' - - if format != _test_export_default: - context.obj.common.warn("Option '--format' is deprecated, please use '--how' instead.") - - how = format - - # TODO: move this "requires bugzilla" flag to export plugin level. - if bugzilla and how not in ('nitrate', 'polarion'): - raise tmt.utils.GeneralError( - "The --bugzilla option is supported only with --nitrate " - "or --polarion for now.") - - if kwargs.get('fmf_id'): - echo(tmt.base.FmfId.export_collection( - collection=[test.fmf_id for test in context.obj.tree.tests()], - format=how, - template=Path(template) if template else None - )) - - else: - echo(tmt.Test.export_collection( - collection=context.obj.tree.tests(), - format=how, - template=Path(template) if template else None - )) - - # ignore[arg-type]: click code expects click.Context, but we use our own type for better type # inference. See Context and ContextObjects above. @tests.command(name="id") # type: ignore[arg-type] @@ -841,57 +715,6 @@ def plans_create( logger=context.obj.logger) -_plan_export_formats = list(tmt.Plan.get_export_plugin_registry().iter_plugin_ids()) -_plan_export_default = 'yaml' - - -@plans.command(name='export') -@pass_context -@filtering_options_long -@option( - '-h', '--how', default=_plan_export_default, show_default=True, - help='Output format.', - choices=_plan_export_formats) -@option( - '--format', default=_plan_export_default, show_default=True, - help='Output format.', - deprecated=Deprecated('1.21', hint='use --how instead'), - choices=_plan_export_formats) -@option( - '-d', '--debug', is_flag=True, - help='Provide as much debugging details as possible.') -# TODO: move to `template` export plugin options -@option( - '--template', metavar='PATH', - help="Path to a template to use for rendering the export. Used with '--how=template' only." - ) -@environment_options -def plans_export( - context: Context, - how: str, - format: str, - template: Optional[str], - **kwargs: Any) -> None: - """ - Export plans into desired format. - - Regular expression can be used to filter plans by name. - Use '.' to select plans under the current working directory. - """ - tmt.Plan.store_cli_invocation(context) - - if format != _test_export_default: - context.obj.common.warn("Option '--format' is deprecated, please use '--how' instead.") - - how = format - - echo(tmt.Plan.export_collection( - collection=context.obj.tree.plans(), - format=how, - template=Path(template) if template else None - )) - - # ignore[arg-type]: click code expects click.Context, but we use our own type for better type # inference. See Context and ContextObjects above. @plans.command(name="id") # type: ignore[arg-type] @@ -1108,70 +931,6 @@ def headfoot(text: str) -> None: echo() -_story_export_formats = list(tmt.Story.get_export_plugin_registry().iter_plugin_ids()) -_story_export_default = 'yaml' - - -@stories.command(name='export') -@pass_context -@filtering_options_long -@story_flags_filter_options -@option( - '-h', '--how', default=_story_export_default, show_default=True, - help='Output format.', - choices=_story_export_formats) -@option( - '--format', default=_story_export_default, show_default=True, - help='Output format.', - deprecated=Deprecated('1.21', hint='use --how instead'), - choices=_story_export_formats) -@option( - '-d', '--debug', is_flag=True, - help='Provide as much debugging details as possible.') -# TODO: move to `template` export plugin options -@option( - '--template', metavar='PATH', - help="Path to a template to use for rendering the export. Used with '--how=rst|template' only." - ) -def stories_export( - context: Context, - how: str, - format: str, - implemented: bool, - verified: bool, - documented: bool, - covered: bool, - unimplemented: bool, - unverified: bool, - undocumented: bool, - uncovered: bool, - template: Optional[str], - **kwargs: Any) -> None: - """ - Export selected stories into desired format. - - Regular expression can be used to filter stories by name. - Use '.' to select stories under the current working directory. - """ - tmt.Story.store_cli_invocation(context) - - if format != _test_export_default: - context.obj.common.warn("Option '--format' is deprecated, please use '--how' instead.") - - how = format - - stories = [ - story for story in context.obj.tree.stories(whole=True) - if story._match(implemented, verified, documented, covered, unimplemented, unverified, - undocumented, uncovered) - ] - - echo(tmt.Story.export_collection( - collection=stories, - format=how, - template=Path(template) if template else None)) - - # ignore[arg-type]: click code expects click.Context, but we use our own type for better type # inference. See Context and ContextObjects above. @stories.command(name="id") # type: ignore[arg-type] diff --git a/tmt/cli/export.py b/tmt/cli/export.py new file mode 100644 index 0000000000..649b4a500e --- /dev/null +++ b/tmt/cli/export.py @@ -0,0 +1,259 @@ +""" ``tmt * export`` implementation """ + +from typing import Any, Optional + +from click import echo + +import tmt.base +import tmt.templates +import tmt.utils +from tmt.cli import Context, pass_context +from tmt.cli._root import ( + environment_options, + filtering_options_long, + plans, + stories, + story_flags_filter_options, + tests, + ) +from tmt.options import Deprecated, option +from tmt.utils import Path + +_test_export_formats = list(tmt.Test.get_export_plugin_registry().iter_plugin_ids()) +_test_export_default = 'yaml' + + +@tests.command(name='export') +@pass_context +@filtering_options_long +@option( + '-h', '--how', default=_test_export_default, show_default=True, + help='Output format.', + choices=_test_export_formats) +@option( + '--format', default=_test_export_default, show_default=True, + help='Output format.', + deprecated=Deprecated('1.21', hint='use --how instead'), + choices=_test_export_formats) +@option( + '--nitrate', is_flag=True, + help="Export test metadata to Nitrate.", + deprecated=Deprecated('1.21', hint="use '--how nitrate' instead")) +@option( + '--project-id', help='Use specific Polarion project ID.') +@option( + '--link-polarion / --no-link-polarion', default=False, is_flag=True, + help='Add Polarion link to fmf testcase metadata') +@option( + '--bugzilla', is_flag=True, + help=""" + Link Nitrate case to Bugzilla specified in the 'link' attribute with the relation + 'verifies'. + """) +@option( + '--ignore-git-validation', is_flag=True, + help=""" + Ignore unpublished git changes and export to Nitrate. The case might not be able to be + scheduled! + """) +@option( + '--append-summary / --no-append-summary', default=False, is_flag=True, + help=""" + Include test summary in the Nitrate/Polarion test case summary as well. By default, only + the repository name and test name are used. + """) +@option( + '--create', is_flag=True, + help="Create test cases in nitrate if they don't exist.") +@option( + '--general / --no-general', default=False, is_flag=True, + help=""" + Link Nitrate case to component's General plan. Disabled by default. Note that this will + unlink any previously connected general plans. + """) +@option( + '--link-runs / --no-link-runs', default=False, is_flag=True, + help=""" + Link Nitrate case to all open runs of descendant plans of General plan. Disabled by + default. Implies --general option. + """) +@option( + '--fmf-id', is_flag=True, + help='Show fmf identifiers instead of test metadata.') +@option( + '--duplicate / --no-duplicate', default=False, show_default=True, is_flag=True, + help=""" + Allow or prevent creating duplicates in Nitrate by searching for existing test cases with + the same fmf identifier. + """) +@option( + '-n', '--dry', is_flag=True, + help="Run in dry mode. No changes, please.") +@option( + '-d', '--debug', is_flag=True, + help='Provide as much debugging details as possible.') +# TODO: move to `template` export plugin options +@option( + '--template', metavar='PATH', + help="Path to a template to use for rendering the export. Used with '--how=template' only." + ) +def tests_export( + context: Context, + format: str, + how: str, + nitrate: bool, + bugzilla: bool, + template: Optional[str], + **kwargs: Any) -> None: + """ + Export test data into the desired format. + + Regular expression can be used to filter tests by name. + Use '.' to select tests under the current working directory. + """ + tmt.Test.store_cli_invocation(context) + + if nitrate: + context.obj.common.warn( + "Option '--nitrate' is deprecated, please use '--how nitrate' instead.") + how = 'nitrate' + + if format != _test_export_default: + context.obj.common.warn("Option '--format' is deprecated, please use '--how' instead.") + + how = format + + # TODO: move this "requires bugzilla" flag to export plugin level. + if bugzilla and how not in ('nitrate', 'polarion'): + raise tmt.utils.GeneralError( + "The --bugzilla option is supported only with --nitrate " + "or --polarion for now.") + + if kwargs.get('fmf_id'): + echo(tmt.base.FmfId.export_collection( + collection=[test.fmf_id for test in context.obj.tree.tests()], + format=how, + template=Path(template) if template else None + )) + + else: + echo(tmt.Test.export_collection( + collection=context.obj.tree.tests(), + format=how, + template=Path(template) if template else None + )) + + +_plan_export_formats = list(tmt.Plan.get_export_plugin_registry().iter_plugin_ids()) +_plan_export_default = 'yaml' + + +@plans.command(name='export') +@pass_context +@filtering_options_long +@option( + '-h', '--how', default=_plan_export_default, show_default=True, + help='Output format.', + choices=_plan_export_formats) +@option( + '--format', default=_plan_export_default, show_default=True, + help='Output format.', + deprecated=Deprecated('1.21', hint='use --how instead'), + choices=_plan_export_formats) +@option( + '-d', '--debug', is_flag=True, + help='Provide as much debugging details as possible.') +# TODO: move to `template` export plugin options +@option( + '--template', metavar='PATH', + help="Path to a template to use for rendering the export. Used with '--how=template' only." + ) +@environment_options +def plans_export( + context: Context, + how: str, + format: str, + template: Optional[str], + **kwargs: Any) -> None: + """ + Export plans into desired format. + + Regular expression can be used to filter plans by name. + Use '.' to select plans under the current working directory. + """ + tmt.Plan.store_cli_invocation(context) + + if format != _test_export_default: + context.obj.common.warn("Option '--format' is deprecated, please use '--how' instead.") + + how = format + + echo(tmt.Plan.export_collection( + collection=context.obj.tree.plans(), + format=how, + template=Path(template) if template else None + )) + + +_story_export_formats = list(tmt.Story.get_export_plugin_registry().iter_plugin_ids()) +_story_export_default = 'yaml' + + +@stories.command(name='export') +@pass_context +@filtering_options_long +@story_flags_filter_options +@option( + '-h', '--how', default=_story_export_default, show_default=True, + help='Output format.', + choices=_story_export_formats) +@option( + '--format', default=_story_export_default, show_default=True, + help='Output format.', + deprecated=Deprecated('1.21', hint='use --how instead'), + choices=_story_export_formats) +@option( + '-d', '--debug', is_flag=True, + help='Provide as much debugging details as possible.') +# TODO: move to `template` export plugin options +@option( + '--template', metavar='PATH', + help="Path to a template to use for rendering the export. Used with '--how=rst|template' only." + ) +def stories_export( + context: Context, + how: str, + format: str, + implemented: bool, + verified: bool, + documented: bool, + covered: bool, + unimplemented: bool, + unverified: bool, + undocumented: bool, + uncovered: bool, + template: Optional[str], + **kwargs: Any) -> None: + """ + Export selected stories into desired format. + + Regular expression can be used to filter stories by name. + Use '.' to select stories under the current working directory. + """ + tmt.Story.store_cli_invocation(context) + + if format != _test_export_default: + context.obj.common.warn("Option '--format' is deprecated, please use '--how' instead.") + + how = format + + stories = [ + story for story in context.obj.tree.stories(whole=True) + if story._match(implemented, verified, documented, covered, unimplemented, unverified, + undocumented, uncovered) + ] + + echo(tmt.Story.export_collection( + collection=stories, + format=how, + template=Path(template) if template else None))