"""
Base classes for rendering.
"""
import collections
import functools
from collections import OrderedDict
from html import escape
from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
from testplan.common.exporters.pdf import RowData as _RowData
from . import constants
RowData = functools.partial(_RowData, num_columns=constants.NUM_COLUMNS)
[docs]
class SlicedParagraph:
"""
Iterator which returns slices of ReportLab Paragraph to make sure each does
not exceed max height (which will trigger ReportLab LayoutError).
:param parts: list of (text, formatter) tuple
:type parts: [(``str``, ``str``), ...]
:param width: width allowed to layout each paragraph
:type width: ``int``
:param height: height allowed to layout each paragraph
:type height: ``int``
:param style: style object for paragraph
:type style: ReportLab ParagraphStyle
"""
def __init__(
self,
parts: Union[List[Tuple[str, str]], Tuple[str, str]],
width: int,
height: int = constants.MAX_CELL_HEIGHT,
style: ParagraphStyle = constants.PARAGRAPH_STYLE,
**kwargs: Any,
) -> None:
self.width = width
self.height = height
text_parts = []
if not isinstance(parts, list):
parts = [parts]
for part, formatter in parts:
# reserve indentation - report lab does not wrap at '\n' and removes space
part = (
escape(part, quote=False)
.replace("\n", "<br/>")
.replace(" ", " ")
)
text_parts.append(formatter.format(part))
self.para = Paragraph(text="".join(text_parts), style=style, **kwargs)
def __next__(self) -> Any:
if self.para:
paras = self.para.split(self.width, self.height)
# paras == [] for empty line
para = paras[0] if paras else ""
self.para = paras[1] if len(paras) == 2 else None
return para
else:
raise StopIteration
def __iter__(self) -> "SlicedParagraph":
return self
[docs]
class BaseRowRenderer:
"""Base class for row renderers."""
always_display = False
def __init__(self, style: Any) -> None:
self.style = style
[docs]
def get_row_data(self, source: Any, depth: int, row_idx: int) -> Any:
"""
Return `RowData` to be rendered on the pdf.
:param source: Source object for the renderer.
:type source: ``report.base.Report`` or ``dict`` (for assertion data).
:param depth: Depth of the source object on report tree.
Used for indentation.
:type depth: ``int``
:param row_idx: Index of the current table row to be rendered.
:type row_idx: ``int``
:return: ``RowData`` object.
:rtype: ``exporters.utils.pdf.RowData``
"""
raise NotImplementedError
[docs]
def get_style(self, source: Any) -> Any:
return self.style.passing
[docs]
def should_display(self, source: Any) -> bool:
"""Use class attribute by default."""
return (
self.always_display
or self.get_style(source).display_assertion_detail
)