plantuml_printer.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  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. """Class to generate files in dot format and image formats supported by Graphviz."""
  5. from __future__ import annotations
  6. from pylint.pyreverse.printer import EdgeType, Layout, NodeProperties, NodeType, Printer
  7. from pylint.pyreverse.utils import get_annotation_label
  8. class PlantUmlPrinter(Printer):
  9. """Printer for PlantUML diagrams."""
  10. DEFAULT_COLOR = "black"
  11. NODES: dict[NodeType, str] = {
  12. NodeType.CLASS: "class",
  13. NodeType.PACKAGE: "package",
  14. }
  15. ARROWS: dict[EdgeType, str] = {
  16. EdgeType.INHERITS: "--|>",
  17. EdgeType.ASSOCIATION: "-->",
  18. EdgeType.COMPOSITION: "--*",
  19. EdgeType.AGGREGATION: "--o",
  20. EdgeType.USES: "-->",
  21. EdgeType.TYPE_DEPENDENCY: "..>",
  22. }
  23. def _open_graph(self) -> None:
  24. """Emit the header lines."""
  25. self.emit("@startuml " + self.title)
  26. if not self.use_automatic_namespace:
  27. self.emit("set namespaceSeparator none")
  28. if self.layout:
  29. if self.layout is Layout.LEFT_TO_RIGHT:
  30. self.emit("left to right direction")
  31. elif self.layout is Layout.TOP_TO_BOTTOM:
  32. self.emit("top to bottom direction")
  33. else:
  34. raise ValueError(
  35. f"Unsupported layout {self.layout}. PlantUmlPrinter only "
  36. "supports left to right and top to bottom layout."
  37. )
  38. def emit_node(
  39. self,
  40. name: str,
  41. type_: NodeType,
  42. properties: NodeProperties | None = None,
  43. ) -> None:
  44. """Create a new node.
  45. Nodes can be classes, packages, participants etc.
  46. """
  47. if properties is None:
  48. properties = NodeProperties(label=name)
  49. nodetype = self.NODES[type_]
  50. if properties.color and properties.color != self.DEFAULT_COLOR:
  51. color = f" #{properties.color.lstrip('#')}"
  52. else:
  53. color = ""
  54. body = []
  55. if properties.attrs:
  56. body.extend(properties.attrs)
  57. if properties.methods:
  58. for func in properties.methods:
  59. args = self._get_method_arguments(func)
  60. line = "{abstract}" if func.is_abstract() else ""
  61. line += f"{func.name}({', '.join(args)})"
  62. if func.returns:
  63. line += " -> " + get_annotation_label(func.returns)
  64. body.append(line)
  65. label = properties.label if properties.label is not None else name
  66. if properties.fontcolor and properties.fontcolor != self.DEFAULT_COLOR:
  67. label = f"<color:{properties.fontcolor}>{label}</color>"
  68. self.emit(f'{nodetype} "{label}" as {name}{color} {{')
  69. self._inc_indent()
  70. for line in body:
  71. self.emit(line)
  72. self._dec_indent()
  73. self.emit("}")
  74. def emit_edge(
  75. self,
  76. from_node: str,
  77. to_node: str,
  78. type_: EdgeType,
  79. label: str | None = None,
  80. ) -> None:
  81. """Create an edge from one node to another to display relationships."""
  82. edge = f"{from_node} {self.ARROWS[type_]} {to_node}"
  83. if label:
  84. edge += f" : {label}"
  85. self.emit(edge)
  86. def _close_graph(self) -> None:
  87. """Emit the lines needed to properly close the graph."""
  88. self.emit("@enduml")