Coverage for docs/utilities/plugindiagram/template_mermaid.py: 0%

123 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 17:09 +0000

1"""Generate mermaid code via templetes respectively template-functions.""" 

2 

3import textwrap 

4from pathlib import Path 

5from bim2sim.plugins import load_plugin 

6 

7def split_string_50(text, max_width=50): 

8 """Split the text after max. 50 characters.""" 

9 wraped_text = textwrap.wrap(text, max_width) 

10 wraped_text = '\n'.join(wraped_text) 

11 return wraped_text 

12 

13 

14def generate_task_code(taskname: str = "bim2simtask", # pylint: disable=too-many-arguments 

15 module_path: str = "module_path", 

16 reads: str = "reads", 

17 touches: str = "touches", 

18 doc: str = "docstring", 

19 reads_touches_vis: bool = True) -> str: 

20 """Generate mermaid code representing a bim2sim task. 

21 

22 WIP: so the some structure stuff like tpye of diagram is added here, later 

23 it should move to def generate_diagram". 

24 

25 Args: 

26 taskname: name of the bim2sim taks of the plugin, no space allowed 

27 module_path: path to the module/task definition 

28 reads: input the task uses (from the central state) 

29 touches: output the task reply (to the central state) 

30 reads_touches_vis: enable/disable the subgraph showing 

31 the reads and touches 

32 

33 Returns: 

34 Mermaid code of an task. 

35 WIP: check how to pipe. now it will printed 

36 

37 Attention: 

38 - Tasknames should be unique, when merge different mermaid 

39 templates instances. 

40 

41 """ 

42 # optional reads and touches subgraph, must defined before the templete 

43 if reads_touches_vis: 

44 code_reads_touches = """ 

45state{taskname}[("state 

46 (reads/touches)")] 

47 """ 

48 code_rt_state = code_reads_touches.format( 

49 taskname=taskname) 

50 code_rt = code_rt_state 

51 # check for reads 

52 if reads != ' - ': 

53 code_rt_reads = """ 

54state{taskname} -- {reads} --> t{taskname} 

55""" 

56 code_rt_reads = code_rt_reads.format( 

57 reads=reads, 

58 taskname=taskname) 

59 code_rt = code_rt + code_rt_reads 

60 # check for touches 

61 if touches != ' - ': 

62 code_rt_touches = """ 

63t{taskname} -- {touches} --> state{taskname} 

64""" 

65 code_rt_touches = code_rt_touches.format( 

66 touches=touches, 

67 taskname=taskname) 

68 code_rt = code_rt + code_rt_touches 

69 else: 

70 code_rt_touches = """direction RL""" 

71 code_rt = code_rt + code_rt_touches 

72 else: 

73 code_rt = "" 

74 

75 code_template = """ 

76subgraph task{taskname}["task {taskname}"] 

77 subgraph "" \n 

78 t{taskname}["{module_path} \n {taskname}"] 

79 ext{taskname}(" {doc} " ) 

80 end 

81{code_rt} 

82end 

83 """ 

84 code = code_template.format(taskname=taskname, 

85 module_path=module_path, 

86 doc=doc, 

87 code_rt=code_rt) 

88 return code 

89 

90 

91def generate_diagram(plugin_infos: list, tasks_infos: list, # pylint: disable=too-many-locals 

92 central_state: bool = False) -> str: 

93 """Print mermaid code of the whole task structure of one bim2sim plugin. 

94 

95 The plugin structure is fix: next plugin is connected to the plugin before. 

96 

97 Args: 

98 plugin_infos: information about the whole plugin [name, module, .. ] 

99 tasks: list of infos of tasks [{name, reads, touches ...}, {...}] 

100 central_state: bool 

101 True: reads and touches connected the the central state 

102 False: reads and touches includes in task element 

103 """ 

104 # header of the mermaid diagram 

105 plugin_name = plugin_infos['name'] 

106 digram_header = """--- 

107title: plugin {plugin_name} 

108--- 

109flowchart TB 

110 """ 

111 mermaid_code = digram_header.format(plugin_name=plugin_name) 

112 

113 # task elements of the mermaid diagram 

114 if central_state: 

115 

116 state_code = """ 

117state[("state: 

118project 

119data storage")] 

120""" 

121 mermaid_code = mermaid_code + state_code 

122 

123 for task_infos in tasks_infos: 

124 taskname = task_infos['name'] 

125 reads = task_infos['reads'] 

126 touches = task_infos['touches'] 

127 task_code = generate_task_code(taskname=taskname, 

128 module_path=task_infos['module_path'], 

129 doc=task_infos['doc_first_sentence'], 

130 reads_touches_vis=False) 

131 mermaid_code = mermaid_code + task_code 

132 # connetion reads and touches of the task to the state 

133 code_connection_state = '' 

134 if touches != ' - ': 

135 code_connection_state_touches = """ 

136t{taskname} -- {touches} --> state\n""" 

137 code_connection_state_touches = code_connection_state_touches.format( 

138 taskname=taskname, 

139 touches=touches) 

140 code_connection_state += code_connection_state_touches 

141 

142 if reads != ' - ': 

143 code_connection_state_reads = """ 

144state -- {reads} --> t{taskname} \n""" 

145 code_connection_state_reads = code_connection_state_reads.format( 

146 taskname=taskname, 

147 reads=reads) 

148 code_connection_state += code_connection_state_reads 

149 

150 mermaid_code = mermaid_code + code_connection_state 

151 # if every task has their own state visualisation 

152 else: 

153 for task_infos in tasks_infos: 

154 task_code = generate_task_code(taskname=task_infos['name'], 

155 module_path=task_infos['module_path'], 

156 reads=task_infos['reads'], 

157 touches=task_infos['touches'], 

158 doc=task_infos['doc_first_sentence'], 

159 reads_touches_vis=True) 

160 mermaid_code = mermaid_code + task_code 

161 # state element 

162 

163 # connections of the task elements of the mermaid diagram 

164 code_connection_templ = """task{taskname_from} --> task{taskname_to} \n""" 

165 

166 code_connections = '' 

167 for i in range(len(tasks_infos) - 1): 

168 code_connection = code_connection_templ.format( 

169 taskname_from=tasks_infos[i]['name'], 

170 taskname_to=tasks_infos[i+1]['name']) 

171 code_connections = code_connections + code_connection 

172 

173 mermaid_code = mermaid_code + code_connections 

174 

175 return mermaid_code 

176 

177 

178def write_file(mermaid_code: str, filename: str): 

179 """Create a file including mermaid code. 

180 

181 Args: 

182 mermaid_code: complete mermaid code which represents the diagram 

183 filename: name of the source code file of the figure 

184 """ 

185 with open(filename, "w", encoding="utf-8") as f: 

186 f.write(mermaid_code) 

187 

188 

189def get_plugin_infos(plugin) -> dict: 

190 """Get plugin infos, like name. 

191 

192 Args: 

193 project: Project object 

194 

195 Return: 

196 dict (name, module) 

197 - name of the plugin 

198 - module the plugin is integrated 

199 """ 

200 plugin_name = plugin.name 

201 plugin_module = plugin.__module__ 

202 

203 plugin_info = {'name': plugin_name, 'module': plugin_module} 

204 return plugin_info 

205 

206 

207def get_task_infos(plugin) -> list: 

208 """Get information of the task of the plugin. 

209 

210 Args: 

211 project: Project object 

212 

213 Return: 

214 list of task names 

215 """ 

216 tasks = plugin.default_tasks 

217 task_infos = [] 

218 for task in tasks: 

219 name = task.__name__ 

220 

221 if len(task.reads) == 0: 

222 reads = ' - ' 

223 else: 

224 reads = ', '.join(task.reads) 

225 

226 if len(task.touches) == 0: 

227 touches = ' - ' 

228 else: 

229 touches = ', '.join(task.touches) 

230 

231 doc = task.__doc__ 

232 doc_first_sentence = str(doc).replace("\n", "").replace(" ", " ") 

233 doc_first_sentence = doc_first_sentence.split(".", maxsplit=1)[0] 

234 doc_first_sentence = doc_first_sentence + '.' 

235 doc_first_sentence = split_string_50(doc_first_sentence) 

236 

237 module = task.__module__ 

238 module_list = str(module).split('.') 

239 path_list = module_list[:-1] 

240 path_list_arrow = [str(item) + ' > ' for item in path_list] 

241 # add line break after third item 

242 if len(path_list_arrow) > 3: 

243 path_list_arrow[2] = str(path_list_arrow[2]) + '\n' 

244 # join list items into a string 

245 path_list_str = ''.join(path_list_arrow) 

246 

247 info = {'name': name, 'reads': reads, 'touches': touches, 

248 'doc': doc, 'doc_first_sentence': doc_first_sentence, 

249 'module_path': path_list_str} 

250 task_infos.append(info) 

251 return task_infos 

252 

253 

254def generate_plugin_structure_fig(path_file: str, 

255 plugin_name: str, 

256 central_state: bool = False): 

257 """Generate mermaid code visulzing the task structure of a plugin. 

258 

259 This generated mermaid code will written into a file (path_name). 

260 To generate a figure, pls use: 

261 - https://mermaid.live 

262 - mmdc (must installed on your system) 

263 - https://github.com/mermaid-js/mermaid-cli 

264 - eg. mmdc -i input.mmd -o output.png -t dark -b transparent 

265 - code can past into github issues (add ```mermaid code ```) 

266 - code can used in Sphinx docuemtation (check for howtos) 

267 - code can used in org-mode of emacs (check for howtos) 

268 Args: 

269 path_file: absolute path to the file (saves mermaid code) 

270 plugin_name: name of the choosen plugin - string 

271 default value: 'teaser' 

272 central_state: bool 

273 True: reads and touches connected the the central state 

274 False: reads and touches includes in task element 

275 

276 Return: 

277 nothing, code is witten into the defined file 

278 

279 """ 

280 try: 

281 plugin = load_plugin(plugin_name) 

282 plugin_infos = get_plugin_infos(plugin) 

283 task_infos = get_task_infos(plugin) 

284 path_name = Path(path_file) # should import the path for all os 

285 write_file(generate_diagram(plugin_infos, task_infos, 

286 central_state=central_state), 

287 path_name) 

288 print('run successful: \nmermaid code was save to:\n' 

289 + str(path_name)) 

290 

291 except ModuleNotFoundError as e: 

292 print(e) 

293 print("Pls, choose a plugin_name like: \n" 

294 + " - 'teaser'\n" 

295 + " - 'energyplus'\n" 

296 + " - 'aixlib'\n" 

297 # + " - 'cfd'\n" 

298 + " - 'HKESim'\n" 

299 + " - 'lca'\n") 

300 

301 except FileNotFoundError as e: 

302 print(e) 

303 print("Pls choose an existing (absolut) path.") 

304 print("At the end of the path add the filename.") 

305 

306 

307if __name__ == '__main__': 

308 

309 # Examples 8 

310 # setup simple plugin, here template decentral state 

311 # visualisation 

312 PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

313 "dynamic/plugindiagram/template_structure_decentral_state.mmd") 

314 generate_plugin_structure_fig(PATH_NAME, 

315 plugin_name='Template', 

316 central_state=False) 

317 # Examples 7 

318 # setup simple plugin, here lca decentral state 

319 # visualisation 

320 PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

321 "dynamic/plugindiagram/lca_structure_decentral_state.mmd") 

322 generate_plugin_structure_fig(PATH_NAME, 

323 plugin_name='lca', 

324 central_state=False) 

325 # Examples 6 

326 # setup simple plugin, here HKESim decentral state 

327 # visualisation 

328 PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

329 "dynamic/plugindiagram/HKESim_structure_decentral_state.mmd") 

330 generate_plugin_structure_fig(PATH_NAME, 

331 plugin_name='HKESim', 

332 central_state=False) 

333 

334 # Examples 5 

335 # setup simple plugin, here aixlib decentral state 

336 # visualisation 

337 PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

338 "dynamic/plugindiagram/aixlib_structure_decentral_state.mmd") 

339 generate_plugin_structure_fig(PATH_NAME, 

340 plugin_name='aixlib', 

341 central_state=False) 

342 # Examples 3 

343 # setup simple plugin, here EnergyPlus decentral state 

344 # visualisation 

345 PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

346 "dynamic/plugindiagram/energyplus_structure_decentral_state.mmd") 

347 generate_plugin_structure_fig(PATH_NAME, 

348 plugin_name='energyplus', 

349 central_state=False) 

350 

351 # Examples 2 

352 # setup simple plugin, here TEASER decentral state 

353 # visualisation 

354 PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

355 "dynamic/plugindiagram/teaser_structure_decentral_state.mmd") 

356 generate_plugin_structure_fig(PATH_NAME, 

357 plugin_name='teaser', 

358 central_state=False) 

359 

360 # Examples 1 

361 # setup simple plugin, here TEASER, central state 

362 PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

363 "dynamic/plugindiagram/teaser_structure_central_state.mmd") 

364 generate_plugin_structure_fig(PATH_NAME, 

365 plugin_name='teaser', 

366 central_state=True) 

367 

368 # # Examples 4 - not working, because cfd plugin not included since 0.2.1 

369 # # setup simple plugin, here cfd decentral state 

370 # # visualisation 

371 # PATH_NAME = ("/home/cudok/Documents/10_Git/bim2sim/docs/source/img/" + 

372 # "dynamic/plugindiagram/cfd_structure_decentral_state.mmd") 

373 # generate_plugin_structure_fig(PATH_NAME, 

374 # plugin_name='cfd', 

375 # central_state=False)