nodes.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. """Micro reports objects.
  5. A micro report is a tree of layout and content objects.
  6. """
  7. from __future__ import annotations
  8. from collections.abc import Iterable, Iterator
  9. from typing import Any, Callable, TypeVar
  10. from pylint.reporters.ureports.base_writer import BaseWriter
  11. _T = TypeVar("_T")
  12. _VNodeT = TypeVar("_VNodeT", bound="VNode")
  13. VisitLeaveFunction = Callable[[_T, Any, Any], None]
  14. class VNode:
  15. def __init__(self) -> None:
  16. self.parent: BaseLayout | None = None
  17. self.children: list[VNode] = []
  18. self.visitor_name: str = self.__class__.__name__.lower()
  19. def __iter__(self) -> Iterator[VNode]:
  20. return iter(self.children)
  21. def accept(self: _VNodeT, visitor: BaseWriter, *args: Any, **kwargs: Any) -> None:
  22. func: VisitLeaveFunction[_VNodeT] = getattr(
  23. visitor, f"visit_{self.visitor_name}"
  24. )
  25. return func(self, *args, **kwargs)
  26. def leave(self: _VNodeT, visitor: BaseWriter, *args: Any, **kwargs: Any) -> None:
  27. func: VisitLeaveFunction[_VNodeT] = getattr(
  28. visitor, f"leave_{self.visitor_name}"
  29. )
  30. return func(self, *args, **kwargs)
  31. class BaseLayout(VNode):
  32. """Base container node.
  33. attributes
  34. * children : components in this table (i.e. the table's cells)
  35. """
  36. def __init__(self, children: Iterable[Text | str] = ()) -> None:
  37. super().__init__()
  38. for child in children:
  39. if isinstance(child, VNode):
  40. self.append(child)
  41. else:
  42. self.add_text(child)
  43. def append(self, child: VNode) -> None:
  44. """Add a node to children."""
  45. assert child not in self.parents()
  46. self.children.append(child)
  47. child.parent = self
  48. def insert(self, index: int, child: VNode) -> None:
  49. """Insert a child node."""
  50. self.children.insert(index, child)
  51. child.parent = self
  52. def parents(self) -> list[BaseLayout]:
  53. """Return the ancestor nodes."""
  54. assert self.parent is not self
  55. if self.parent is None:
  56. return []
  57. return [self.parent] + self.parent.parents()
  58. def add_text(self, text: str) -> None:
  59. """Shortcut to add text data."""
  60. self.children.append(Text(text))
  61. # non container nodes #########################################################
  62. class Text(VNode):
  63. """A text portion.
  64. attributes :
  65. * data : the text value as an encoded or unicode string
  66. """
  67. def __init__(self, data: str, escaped: bool = True) -> None:
  68. super().__init__()
  69. self.escaped = escaped
  70. self.data = data
  71. class VerbatimText(Text):
  72. """A verbatim text, display the raw data.
  73. attributes :
  74. * data : the text value as an encoded or unicode string
  75. """
  76. # container nodes #############################################################
  77. class Section(BaseLayout):
  78. """A section.
  79. attributes :
  80. * BaseLayout attributes
  81. a title may also be given to the constructor, it'll be added
  82. as a first element
  83. a description may also be given to the constructor, it'll be added
  84. as a first paragraph
  85. """
  86. def __init__(
  87. self,
  88. title: str | None = None,
  89. description: str | None = None,
  90. children: Iterable[Text | str] = (),
  91. ) -> None:
  92. super().__init__(children=children)
  93. if description:
  94. self.insert(0, Paragraph([Text(description)]))
  95. if title:
  96. self.insert(0, Title(children=(title,)))
  97. self.report_id: str = "" # Used in ReportHandlerMixin.make_reports
  98. class EvaluationSection(Section):
  99. def __init__(self, message: str, children: Iterable[Text | str] = ()) -> None:
  100. super().__init__(children=children)
  101. title = Paragraph()
  102. title.append(Text("-" * len(message)))
  103. self.append(title)
  104. message_body = Paragraph()
  105. message_body.append(Text(message))
  106. self.append(message_body)
  107. class Title(BaseLayout):
  108. """A title.
  109. attributes :
  110. * BaseLayout attributes
  111. A title must not contain a section nor a paragraph!
  112. """
  113. class Paragraph(BaseLayout):
  114. """A simple text paragraph.
  115. attributes :
  116. * BaseLayout attributes
  117. A paragraph must not contains a section !
  118. """
  119. class Table(BaseLayout):
  120. """Some tabular data.
  121. attributes :
  122. * BaseLayout attributes
  123. * cols : the number of columns of the table (REQUIRED)
  124. * rheaders : the first row's elements are table's header
  125. * cheaders : the first col's elements are table's header
  126. * title : the table's optional title
  127. """
  128. def __init__(
  129. self,
  130. cols: int,
  131. title: str | None = None,
  132. rheaders: int = 0,
  133. cheaders: int = 0,
  134. children: Iterable[Text | str] = (),
  135. ) -> None:
  136. super().__init__(children=children)
  137. assert isinstance(cols, int)
  138. self.cols = cols
  139. self.title = title
  140. self.rheaders = rheaders
  141. self.cheaders = cheaders