printer.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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. """Base class defining the interface for a printer."""
  5. from __future__ import annotations
  6. from abc import ABC, abstractmethod
  7. from enum import Enum
  8. from typing import NamedTuple
  9. from astroid import nodes
  10. from pylint.pyreverse.utils import get_annotation_label
  11. class NodeType(Enum):
  12. CLASS = "class"
  13. INTERFACE = "interface"
  14. PACKAGE = "package"
  15. class EdgeType(Enum):
  16. INHERITS = "inherits"
  17. IMPLEMENTS = "implements"
  18. ASSOCIATION = "association"
  19. AGGREGATION = "aggregation"
  20. USES = "uses"
  21. class Layout(Enum):
  22. LEFT_TO_RIGHT = "LR"
  23. RIGHT_TO_LEFT = "RL"
  24. TOP_TO_BOTTOM = "TB"
  25. BOTTOM_TO_TOP = "BT"
  26. class NodeProperties(NamedTuple):
  27. label: str
  28. attrs: list[str] | None = None
  29. methods: list[nodes.FunctionDef] | None = None
  30. color: str | None = None
  31. fontcolor: str | None = None
  32. class Printer(ABC):
  33. """Base class defining the interface for a printer."""
  34. def __init__(
  35. self,
  36. title: str,
  37. layout: Layout | None = None,
  38. use_automatic_namespace: bool | None = None,
  39. ) -> None:
  40. self.title: str = title
  41. self.layout = layout
  42. self.use_automatic_namespace = use_automatic_namespace
  43. self.lines: list[str] = []
  44. self._indent = ""
  45. self._open_graph()
  46. def _inc_indent(self) -> None:
  47. """Increment indentation."""
  48. self._indent += " "
  49. def _dec_indent(self) -> None:
  50. """Decrement indentation."""
  51. self._indent = self._indent[:-2]
  52. @abstractmethod
  53. def _open_graph(self) -> None:
  54. """Emit the header lines, i.e. all boilerplate code that defines things like
  55. layout etc.
  56. """
  57. def emit(self, line: str, force_newline: bool | None = True) -> None:
  58. if force_newline and not line.endswith("\n"):
  59. line += "\n"
  60. self.lines.append(self._indent + line)
  61. @abstractmethod
  62. def emit_node(
  63. self,
  64. name: str,
  65. type_: NodeType,
  66. properties: NodeProperties | None = None,
  67. ) -> None:
  68. """Create a new node.
  69. Nodes can be classes, packages, participants etc.
  70. """
  71. @abstractmethod
  72. def emit_edge(
  73. self,
  74. from_node: str,
  75. to_node: str,
  76. type_: EdgeType,
  77. label: str | None = None,
  78. ) -> None:
  79. """Create an edge from one node to another to display relationships."""
  80. @staticmethod
  81. def _get_method_arguments(method: nodes.FunctionDef) -> list[str]:
  82. if method.args.args is None:
  83. return []
  84. first_arg = 0 if method.type in {"function", "staticmethod"} else 1
  85. arguments: list[nodes.AssignName] = method.args.args[first_arg:]
  86. annotations = dict(zip(arguments, method.args.annotations[first_arg:]))
  87. for arg in arguments:
  88. annotation_label = ""
  89. ann = annotations.get(arg)
  90. if ann:
  91. annotation_label = get_annotation_label(ann)
  92. annotations[arg] = annotation_label
  93. return [
  94. f"{arg.name}: {ann}" if ann else f"{arg.name}"
  95. for arg, ann in annotations.items()
  96. ]
  97. def generate(self, outputfile: str) -> None:
  98. """Generate and save the final outputfile."""
  99. self._close_graph()
  100. with open(outputfile, "w", encoding="utf-8") as outfile:
  101. outfile.writelines(self.lines)
  102. @abstractmethod
  103. def _close_graph(self) -> None:
  104. """Emit the lines needed to properly close the graph."""