diff --git a/pyproject.toml b/pyproject.toml index c013721..f3a7334 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "robotframework-testdoc" -version = "0.2.3" +version = "0.2.4" description = "A CLI Tool to generate a Test Documentation for your RobotFramework Test Scripts." readme = "README.md" requires-python = ">=3.7" diff --git a/src/testdoc/cli.py b/src/testdoc/cli.py index adc1be1..dd48499 100644 --- a/src/testdoc/cli.py +++ b/src/testdoc/cli.py @@ -25,7 +25,7 @@ @click.option("-c", "--configfile", required=False, help="Optional .toml configuration file (includes all cmd-args)") @click.option("-v", "--verbose", is_flag=True, required=False, help="More precise debugging into shell") @click.version_option(package_name='robotframework-testdoc') -@click.argument("PATH") +@click.argument("PATH", nargs=-1, required=True) @click.argument("OUTPUT") def main( title, diff --git a/src/testdoc/helper/pathconverter.py b/src/testdoc/helper/pathconverter.py index 035723e..10f2e38 100644 --- a/src/testdoc/helper/pathconverter.py +++ b/src/testdoc/helper/pathconverter.py @@ -15,11 +15,13 @@ def path_convertion(self) -> str: config_path = self.args.config_file # Convert path to suite file / directory - suite_path = PathConverter().conv_generic_path(path=suite_path) - if ".robot" in suite_path: - msg = f'Suite File: "{str(suite_path).split("/")[-1]}"' + if type(suite_path) is tuple: + suite_path = list(suite_path) + for idx, item in enumerate(suite_path): + _mod = PathConverter().conv_generic_path(item) + suite_path[idx] = _mod else: - msg = f"Suite Directory: '{suite_path}'" + suite_path = PathConverter().conv_generic_path(path=suite_path) # Convert path to output file output_path = PathConverter().conv_generic_path(path=output_path) @@ -30,6 +32,16 @@ def path_convertion(self) -> str: # Print to console if self.args.verbose_mode: + msg = "" + if type(suite_path) is not list: + suite_path = list(suite_path) + + for item in suite_path: + if ".robot" in suite_path: + msg += f'Suite File: "{str(suite_path).split("/")[-1]}"\n' + else: + msg += f"Suite Directory: '{suite_path}'\n" + Logger().Log("=== TestDoc ===") Logger().LogKeyValue("Generating Test Documentation for: ", msg) Logger().LogKeyValue("Saving to output file: ", output_path) diff --git a/src/testdoc/parser/testsuiteparser.py b/src/testdoc/parser/testsuiteparser.py index fe2c24c..968c63f 100644 --- a/src/testdoc/parser/testsuiteparser.py +++ b/src/testdoc/parser/testsuiteparser.py @@ -1,15 +1,27 @@ +# Portions of this file are derived from Robot Framework, licensed under the Apache License 2.0. +# Derived code: see class `RobotSuiteFiltering`. import os from pathlib import Path from robot.api import SuiteVisitor, TestSuite from .testcaseparser import TestCaseParser from .modifier.suitefilemodifier import SuiteFileModifier +from ..helper.cliargs import CommandLineArguments +from..helper.pathconverter import PathConverter + +from robot.conf import RobotSettings +from robot.running import TestSuiteBuilder +from robot.testdoc import USAGE +from robot.utils import ( + abspath, Application, is_list_like +) class RobotSuiteParser(SuiteVisitor): def __init__(self): self.suite_counter = 0 self.suites = [] self.tests = [] + self.args = CommandLineArguments().data def visit_suite(self, suite): @@ -19,7 +31,7 @@ def visit_suite(self, suite): # Test Suite Parser suite_info = { "id": str(suite.longname).lower().replace(".", "_").replace(" ", "_"), - "filename": str(Path(suite.source).name), + "filename": str(Path(suite.source).name) if suite.source else suite.name, "name": suite.name, "doc": "
".join(line.replace("\\n","") for line in suite.doc.splitlines() if line.strip()) if suite.doc else None, "is_folder": self._is_directory(suite), @@ -41,9 +53,14 @@ def visit_suite(self, suite): suite_info["total_tests"] = total_tests self.suites.append(suite_info) - def parse_suite(self, suite_path): - suite = TestSuite.from_file_system(suite_path) - suite = TestCaseParser().consider_tags(suite) + def parse_suite(self): + # Use official Robot Framework Application Package to parse cli arguments and modify suite object. + robot_options = self._convert_args() + _rfs = RobotSuiteFiltering() + _rfs.execute_cli(robot_options, False) + suite = _rfs._suite_object + + # Custom suite object modification with new test doc library suite = SuiteFileModifier()._modify_root_suite_details(suite) suite.visit(self) return self.suites @@ -72,3 +89,60 @@ def _already_parsed(self, suite): existing_suite = next((s for s in self.suites if s["name"] == suite.name), None) if existing_suite: return + + def _convert_args(self): + """ Convert given cli args to match internal robotframework syntax """ + _include = self.args.include + _exclude = self.args.exclude + _source = self.args.suite_file + # Type Conversions + if type(_include) is not list: + _include = list(_include) + if type(_exclude) is not list: + _exclude = list(_exclude) + if type(_source) is not list: + _source = list(_source) + + # Format / Syntax Conversions + robot_options = [] + for item in _include: + robot_options.append("-i") + robot_options.append(f"{item}") + for item in _exclude: + robot_options.append("-e") + robot_options.append(f"{item}") + for item in _source: + _os_indep_path = PathConverter().conv_generic_path(item) + robot_options.append(f"{_os_indep_path}") + robot_options.append(self.args.output_file) + return robot_options + +class RobotSuiteFiltering(Application): + """ Use official RF Application package to build test suite object with given cli options & arguments """ + OPTIONS = """ +Options +======= +NOT SUPPORTED YET: -T --title title Set the title of the generated documentation. + Underscores in the title are converted to spaces. + The default title is the name of the top level suite. +NOT SUPPORTED YET: -N --name name Override the name of the top level suite. +NOT SUPPORTED YET: -D --doc document Override the documentation of the top level suite. +NOT SUPPORTED YET: -M --metadata name:value * Set/override metadata of the top level suite. +NOT SUPPORTED YET: -G --settag tag * Set given tag(s) to all test cases. +NOT SUPPORTED YET: -t --test name * Include tests by name. +NOT SUPPORTED YET: -s --suite name * Include suites by name. + -i --include tag * Include tests by tags. + -e --exclude tag * Exclude tests by tags. +""" + def __init__(self): + self._suite_object = None + Application.__init__(self, USAGE, arg_limits=(2,)) + + def main(self, datasources, title=None, **options): + abspath(datasources.pop()) + settings = RobotSettings(options) + if not is_list_like(datasources): + datasources = [datasources] + suite = TestSuiteBuilder(process_curdir=False).build(*datasources) + suite.configure(**settings.suite_config) + self._suite_object = suite diff --git a/src/testdoc/testdoc.py b/src/testdoc/testdoc.py index ab78f47..77b35bc 100644 --- a/src/testdoc/testdoc.py +++ b/src/testdoc/testdoc.py @@ -10,7 +10,7 @@ def main(self): suite_path, output_path, config_path = PathConverter().path_convertion() # Parse suite object & return complete suite object with all information - suite_object = RobotSuiteParser().parse_suite(suite_path) + suite_object = RobotSuiteParser().parse_suite() # Run SuiteFileModifier to modify the test suite object suite_object = SuiteFileModifier().run(suite_object)