Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .github/workflows/build-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ 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:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*

27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand All @@ -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)
2 changes: 1 addition & 1 deletion atest/config/config_with_colors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ verbose_mode = true

[colors]
# DEFAULT THEME:
default = "robot"
default = "dark"
# OR CUSTOM THEME:
# background = "#000028"
# inner_color = "#000028"
Expand Down
Binary file added docs/style_blue.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/style_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/style_robot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/testdoc/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from testdoc.cli import main

if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions src/testdoc/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -36,6 +37,7 @@ def main(
hide_suite_doc,
hide_source,
hide_keywords,
style,
configfile,
verbose,
path,
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/testdoc/default.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Find here some default settings like the used default theme
[default]
theme = "dark"
1 change: 1 addition & 0 deletions src/testdoc/helper/cliargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
26 changes: 20 additions & 6 deletions src/testdoc/html/themes/theme_config.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
54 changes: 43 additions & 11 deletions src/testdoc/parser/modifier/sourceprefixmodifier.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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"
Expand Down Expand Up @@ -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:
Expand All @@ -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]
########################################
36 changes: 10 additions & 26 deletions src/testdoc/parser/modifier/suitefilemodifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 = "<br>".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

#############################################################################################################################

Expand Down
1 change: 1 addition & 0 deletions src/testdoc/parser/testcaseparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions src/testdoc/parser/testsuiteparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -23,7 +24,7 @@ def visit_suite(self, suite):
"total_tests": 0,
"tests": [],
"sub_suites": [],
"metadata": None
"metadata": "<br>".join([f"{k}: {v}" for k, v in suite.metadata.items()]) if suite.metadata else None
}

# Parse Test Cases
Expand All @@ -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

Expand All @@ -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
return