flowchart_generator.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. from pypdevs.DEVS import CoupledDEVS, Port
  2. class FlowchartGenerator:
  3. def __init__(self, model: CoupledDEVS) -> None:
  4. self.model = model
  5. self._tab_level: int = 0
  6. self._result: str = ""
  7. def _add(self, text: str) -> None:
  8. """ Add text to the result, with the appropriate tab level"""
  9. self._result += ('\t' * self._tab_level) + text + '\n'
  10. def _tab(self):
  11. """ Increase tab level """
  12. self._tab_level += 1
  13. def _untab(self):
  14. """ Decrease tab level """
  15. self._tab_level = max(0, self._tab_level - 1)
  16. @staticmethod
  17. def _get_port_name(port: Port) -> (str, str):
  18. """ Output port names, first element is id, second is shorthand name"""
  19. names = [port.name]
  20. parent = port.host_DEVS
  21. while parent is not None:
  22. names.append(parent.name)
  23. try:
  24. parent = parent.host_DEVS
  25. except AttributeError:
  26. parent = None
  27. identifier = '_'.join(reversed(names))
  28. shorthand = names[0]
  29. return identifier, shorthand
  30. def _add_component(self, component):
  31. self._add(f"subgraph {component.name}")
  32. self._tab()
  33. for port in set(component.IPorts) | set(component.OPorts):
  34. port_name = self._get_port_name(port)
  35. self._add(f"{port_name[0]}(\"{port_name[1]}\")")
  36. # Create invisible links between the ports
  37. for i, iport in enumerate(component.IPorts):
  38. iport_name = self._get_port_name(iport)
  39. if len(component.OPorts) <= i:
  40. break
  41. oport_name = self._get_port_name(component.OPorts[i])
  42. self._add(f"{iport_name[0]} ~~~ {oport_name[0]}")
  43. if hasattr(component, "component_set"):
  44. for sub_component in component.component_set:
  45. self._add_component(sub_component)
  46. self._untab()
  47. self._add(f"end")
  48. self._add_component_connections(component)
  49. def _add_component_connections(self, component):
  50. # Go over all connections and connect them to the appropriate ends.
  51. for port_out in component.OPorts + component.IPorts:
  52. port_out_name = self._get_port_name(port_out)
  53. for port_in in port_out.outline:
  54. port_in_name = self._get_port_name(port_in)
  55. # Add this connection
  56. self._add(f"{port_out_name[0]} --> {port_in_name[0]}")
  57. def generate(self, orientation: str = "LR") -> str:
  58. """
  59. Generates a mermaid flowchart for your coupled-DEVS model.
  60. :return: a string containing the mermaid flowchart.
  61. """
  62. self._result = "" # clear previous result
  63. self._add("---")
  64. self._add(f"title: {self.model.name}")
  65. self._add("---")
  66. self._add(f"flowchart {orientation}")
  67. self._tab()
  68. # Declare all submodels with their ports
  69. for component in self.model.component_set:
  70. self._add_component(component)
  71. self._untab()
  72. return self._result
  73. def generate_file(self, filepath: str = "flowchart.md", orientation: str = "LR") -> None:
  74. """ Generates a flowchart for a DEVS model and writes it to a file."""
  75. try:
  76. with open(filepath, 'w', encoding='utf-8') as file:
  77. file.write("```mermaid\n")
  78. file.write(self.generate(orientation))
  79. file.write("```")
  80. except OSError as e:
  81. print(f"Error writing to file: {e}")
  82. raise
  83. if __name__ == '__main__':
  84. from devs_models.fischertechnik_factory import FischertechnikFactory
  85. mdl = FischertechnikFactory("FischertechnikFactory")
  86. fg = FlowchartGenerator(mdl)
  87. chart = fg.generate(orientation="LR")
  88. print(chart)