diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 3a96c2d..e058aa9 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -2,14 +2,19 @@ name: Build Python Package on: push: + branches: + - main + - '**' tags-ignore: - 'v*.*.*' paths: + - '**.yml' - '**.py' - '**.html' - pyproject.toml pull_request: paths: + - '**.yml' - '**.py' - '**.html' - pyproject.toml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9de6644..f604cfb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,12 +40,6 @@ jobs: - name: Build Package run: python -m build - - name: Publish to PyPI - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: twine upload dist/* - - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: @@ -53,3 +47,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload dist/* + diff --git a/README.md b/README.md index 31b33fc..bb27499 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,8 @@ You can select between several themes (color configurations) for your HTML docum ### Default Themes -There are a few predefined default themes available that you can choose via the toml-configuration file. -Therefore, please use the following syntax: +There are a few predefined default themes available that you can choose via the toml-configuration file OR cli. +Therefore, please use the following syntax: for the toml file ```toml [colors] # Use the default theme @@ -110,6 +110,15 @@ default = "robot" default = 3 ``` +For setting it via CLI, please use the following: +```shell +# Applying dark theme +testdoc ... -S dark PATH OUTPUT_FILE + +# Applying blue theme +testdoc ... --style blue PATH OUTPUT_FILE +``` + > [!TIP] > You can select the default theme using either a string value or an integer value. @@ -131,3 +140,17 @@ robot_icon = "#00ffb9" > [!TIP] > Please make sure to configure all available color values from this example — missing values may cause layout or rendering issues in the generated HTML document! + +### Default Themes - Screenshot + +#### Dark + +![alt text](docs/style_dark.png) + +#### Blue + +![alt text](docs/style_blue.png) + +#### Robot / Default + +![alt text](docs/style_robot.png) \ No newline at end of file diff --git a/atest/config/config_with_colors.toml b/atest/config/config_with_colors.toml index 193392a..3e38030 100644 --- a/atest/config/config_with_colors.toml +++ b/atest/config/config_with_colors.toml @@ -4,7 +4,7 @@ verbose_mode = true [colors] # DEFAULT THEME: -default = "robot" +default = "dark" # OR CUSTOM THEME: # background = "#000028" # inner_color = "#000028" diff --git a/docs/style_blue.png b/docs/style_blue.png new file mode 100644 index 0000000..d2570da Binary files /dev/null and b/docs/style_blue.png differ diff --git a/docs/style_dark.png b/docs/style_dark.png new file mode 100644 index 0000000..c95fe04 Binary files /dev/null and b/docs/style_dark.png differ diff --git a/docs/style_robot.png b/docs/style_robot.png new file mode 100644 index 0000000..5bbae0e Binary files /dev/null and b/docs/style_robot.png differ diff --git a/pyproject.toml b/pyproject.toml index 898d5e9..8d9480b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "robotframework-testdoc" -version = "0.1.4" +version = "0.1.5" 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/__main__.py b/src/testdoc/__main__.py index 95f3538..502a396 100644 --- a/src/testdoc/__main__.py +++ b/src/testdoc/__main__.py @@ -1,3 +1,4 @@ from testdoc.cli import main + if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/testdoc/cli.py b/src/testdoc/cli.py index 1052117..5f63b9d 100644 --- a/src/testdoc/cli.py +++ b/src/testdoc/cli.py @@ -19,6 +19,7 @@ @click.option("--hide-suite-doc", is_flag=True, required=False, help="If given, suite documentation is hidden") @click.option("--hide-source", is_flag=True, required=False, help="If given, test suite/ test case source is hidden") @click.option("--hide-keywords", is_flag=True, required=False, help="If given, keyword calls in test cases are hidden") +@click.option("-S", "--style", required=False, help="Choose a predefined default style theme - 'default', 'robot', 'dark' or 'blue' ") @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.argument("PATH") @@ -36,6 +37,7 @@ def main( hide_suite_doc, hide_source, hide_keywords, + style, configfile, verbose, path, @@ -77,6 +79,7 @@ def main( "hide_source": hide_source or None, "hide_keywords": hide_keywords or None, "verbose_mode": verbose or None, + "style": style or None, "config_file": configfile or None, } args.suite_file = path diff --git a/src/testdoc/default.toml b/src/testdoc/default.toml new file mode 100644 index 0000000..812d283 --- /dev/null +++ b/src/testdoc/default.toml @@ -0,0 +1,3 @@ +# Find here some default settings like the used default theme +[default] +theme = "dark" diff --git a/src/testdoc/helper/cliargs.py b/src/testdoc/helper/cliargs.py index 764734b..f7cd379 100644 --- a/src/testdoc/helper/cliargs.py +++ b/src/testdoc/helper/cliargs.py @@ -19,6 +19,7 @@ class CommandLineArgumentsData: config_file: str = None verbose_mode: bool = False suite_file: str = None + style: str = None output_file: str = None colors: dict = None diff --git a/src/testdoc/html/themes/theme_config.py b/src/testdoc/html/themes/theme_config.py index c94e64d..21d7698 100644 --- a/src/testdoc/html/themes/theme_config.py +++ b/src/testdoc/html/themes/theme_config.py @@ -1,18 +1,32 @@ from ...helper.cliargs import CommandLineArguments from .themes import DEFAULT_THEME, ROBOT_THEME, DARK_THEME, BLUE_THEME +import os +import tomli + class ThemeConfig(): + default_config = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "default.toml") + def __init__(self): self.args = CommandLineArguments().data + with open(self.default_config, "rb") as file: + self.config = tomli.load(file) + + ####################################################################################################### def theme(self): - _theme = self.args.colors - if _theme: - if "default" in _theme: - return self._get_predefined_theme(_theme.get("default")) - return _theme - return DARK_THEME + _cli_style = self.args.style + if _cli_style: + return self._get_predefined_theme(_cli_style) + _toml_theme = self.args.colors + if _toml_theme: + if "default" in _toml_theme: + return self._get_predefined_theme(_toml_theme.get("default")) + return _toml_theme + return self._get_predefined_theme(self.config["default"]["theme"]) + + ####################################################################################################### def _get_predefined_theme(self, theme: str): theme = theme.strip() diff --git a/src/testdoc/parser/modifier/sourceprefixmodifier.py b/src/testdoc/parser/modifier/sourceprefixmodifier.py index 1ae33a4..7219d38 100644 --- a/src/testdoc/parser/modifier/sourceprefixmodifier.py +++ b/src/testdoc/parser/modifier/sourceprefixmodifier.py @@ -1,23 +1,45 @@ import os +from abc import ABC, abstractmethod from robot.api import TestSuite from ...helper.cliargs import CommandLineArguments from ...helper.logger import Logger +available_implementations = "gitlab" + +######################################## +# Interface +######################################## +class SourceModifier(ABC): + @abstractmethod + def apply(self, suite_dict, prefix): + pass + +######################################## +# Factory +######################################## +class SourceModifierFactory: + @staticmethod + def get_modifier(prefix_type: str) -> SourceModifier: + if prefix_type.lower() == "gitlab": + return GitLabModifier() + # EXAMPLE Extension: + # elif prefix_type.lower() == "github": + # return GitHubModifier() + raise ValueError( + f"No source modifier found for type '{prefix_type}' - actually available implementation are:\n{available_implementations}" + ) + +######################################## +# Prefix Modifier - Implementation +######################################## class SourcePrefixModifier(): GITLAB_CONNECTOR = "-/blob/main/" def __init__(self): self.args = CommandLineArguments().data - - def _modify(self, suite: TestSuite, prefix: str): - prefix_type, prefix = self._prefix_validation(prefix) - if "gitlab" in prefix_type: - SourcePrefixGitLab()._apply_gitlab_source_to_suite(suite, prefix) - else: - raise ValueError(f"No matching source-prefix modifier found for: {prefix_type} with prefix: {prefix}") def _prefix_validation(self, prefix: str) -> list: if "::" not in prefix: @@ -27,11 +49,16 @@ def _prefix_validation(self, prefix: str) -> list: def modify_source_prefix(self, suite_object: TestSuite) -> TestSuite: Logger().LogKeyValue("Using Prefix for Source: ", self.args.sourceprefix, "yellow") if self.args.verbose_mode else None + prefix_type, prefix = self._prefix_validation(self.args.sourceprefix) + modifier = SourceModifierFactory.get_modifier(prefix_type) for suite in suite_object: - self._modify(suite, self.args.sourceprefix) + modifier.apply(suite, prefix) return suite_object -class SourcePrefixGitLab(): +######################################## +# Low-Level Implementation for GitLab +######################################## +class GitLabModifier(): """ Source Prefix Modifier for "GitLab" Projects. Expected CMD Line Arg: "gitlab::prefix" @@ -62,7 +89,7 @@ def _convert_to_gitlab_url(self, file_path, prefix): rel_path = os.path.relpath(file_path, git_root).replace(os.sep, "/") return prefix.rstrip("/") + "/-/blob/" + git_branch + "/" + rel_path - def _apply_gitlab_source_to_suite(self, suite_dict, prefix): + def apply(self, suite_dict, prefix): try: suite_dict["source"] = self._convert_to_gitlab_url(suite_dict["source"], prefix) except: @@ -75,4 +102,9 @@ def _apply_gitlab_source_to_suite(self, suite_dict, prefix): test["source"] = "GitLink error" for sub_suite in suite_dict.get("sub_suites", []): - self._apply_gitlab_source_to_suite(sub_suite, prefix) + self.apply(sub_suite, prefix) + +######################################## +# Low-Level Implementation for ... +# [FUTURE EXTENSIONS LIKE GITHUB] +######################################## \ No newline at end of file diff --git a/src/testdoc/parser/modifier/suitefilemodifier.py b/src/testdoc/parser/modifier/suitefilemodifier.py index 7039e8c..1c0048e 100644 --- a/src/testdoc/parser/modifier/suitefilemodifier.py +++ b/src/testdoc/parser/modifier/suitefilemodifier.py @@ -18,9 +18,6 @@ def run(self, suite_object: TestSuite = None): self.suite = suite_object # Modify generic params / hide some params - self._modify_root_suite_name() - self._modify_root_suite_doc() - self._modify_root_suite_metadata() self._modify_tags() self._modify_test_doc() self._modify_suite_doc() @@ -29,29 +26,16 @@ def run(self, suite_object: TestSuite = None): return self.suite ############################################################################################################################# - - def _modify_root_suite_name(self): - if not self.args.name: - return - Logger().LogKeyValue("Modified Name of Root Suite: ", self.args.name, "yellow") if self.args.verbose_mode else None - self.suite[0]["name"] = self.args.name - - ############################################################################################################################# - - def _modify_root_suite_doc(self): - if not self.args.doc: - return - Logger().LogKeyValue("Modified Doc of Root Suite: ", self.args.name, "yellow") if self.args.verbose_mode else None - self.suite[0]["doc"] = self.args.doc - - ############################################################################################################################# - - def _modify_root_suite_metadata(self): - if not self.args.metadata: - return - Logger().LogKeyValue("Modified Metadata of Root Suite: ", self.args.metadata, "yellow") if self.args.verbose_mode else None - formatted_metadata = "
".join([f"{k}: {v}" for k, v in self.args.metadata.items()]) - self.suite[0]["metadata"] = formatted_metadata + + # Modify name, doc & metadata via officially provided robot api + def _modify_root_suite_details(self, suite: TestSuite): + if self.args.name: + suite.configure(name=self.args.name) + if self.args.doc: + suite.configure(doc=self.args.doc) + if self.args.metadata: + suite.configure(metadata=self.args.metadata) + return suite ############################################################################################################################# diff --git a/src/testdoc/parser/testcaseparser.py b/src/testdoc/parser/testcaseparser.py index 1662d7d..1ce0c05 100644 --- a/src/testdoc/parser/testcaseparser.py +++ b/src/testdoc/parser/testcaseparser.py @@ -23,6 +23,7 @@ def parse_test(self, suite_info["tests"].append(test_info) return suite_info + # Consider tags via officially provided robot api def consider_tags(self, suite: TestSuite) -> TestSuite: if len(self.args.include) > 0: suite.configure(include_tags=self.args.include) diff --git a/src/testdoc/parser/testsuiteparser.py b/src/testdoc/parser/testsuiteparser.py index fe06638..606ca5a 100644 --- a/src/testdoc/parser/testsuiteparser.py +++ b/src/testdoc/parser/testsuiteparser.py @@ -2,6 +2,7 @@ from robot.api import SuiteVisitor, TestSuite from .testcaseparser import TestCaseParser +from .modifier.suitefilemodifier import SuiteFileModifier class RobotSuiteParser(SuiteVisitor): def __init__(self): @@ -23,7 +24,7 @@ def visit_suite(self, suite): "total_tests": 0, "tests": [], "sub_suites": [], - "metadata": None + "metadata": "
".join([f"{k}: {v}" for k, v in suite.metadata.items()]) if suite.metadata else None } # Parse Test Cases @@ -39,6 +40,7 @@ def visit_suite(self, suite): def parse_suite(self, suite_path): suite = TestSuite.from_file_system(suite_path) suite = TestCaseParser().consider_tags(suite) + suite = SuiteFileModifier()._modify_root_suite_details(suite) suite.visit(self) return self.suites @@ -65,4 +67,4 @@ def _is_directory(self, suite) -> bool: def _already_parsed(self, suite): existing_suite = next((s for s in self.suites if s["name"] == suite.name), None) if existing_suite: - return \ No newline at end of file + return