autosar-calltree 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,488 @@
1
+ """
2
+ Mermaid sequence diagram generator.
3
+
4
+ This module generates Mermaid sequence diagram syntax from call trees,
5
+ outputting markdown files with embedded diagrams.
6
+
7
+ Requirements:
8
+ - SWR_MERMAID_00001: Module-Based Participants
9
+ - SWR_MERMAID_00002: Module Column in Function Table
10
+ - SWR_MERMAID_00003: Fallback Behavior
11
+ """
12
+
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import Dict, List, Optional
16
+
17
+ from ..database.models import AnalysisResult, CallTreeNode, FunctionInfo
18
+
19
+
20
+ class MermaidGenerator:
21
+ """
22
+ Generates Mermaid sequence diagrams from call trees.
23
+
24
+ This class converts call tree structures into Mermaid diagram syntax,
25
+ creates markdown documents with metadata, and handles formatting options.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ abbreviate_rte: bool = True,
31
+ use_module_names: bool = False,
32
+ include_returns: bool = False,
33
+ ):
34
+ """
35
+ Initialize the Mermaid generator.
36
+
37
+ Args:
38
+ abbreviate_rte: Whether to abbreviate long RTE function names
39
+ use_module_names: Use SW module names as participants instead of function names
40
+ include_returns: Whether to include return statements in the sequence diagram (default: False)
41
+ """
42
+ self.abbreviate_rte = abbreviate_rte
43
+ self.use_module_names = use_module_names
44
+ self.include_returns = include_returns
45
+ self.participant_map: Dict[str, str] = {} # Map full names to abbreviated names
46
+ self.next_participant_id = 1
47
+
48
+ def generate(
49
+ self,
50
+ result: AnalysisResult,
51
+ output_path: str,
52
+ include_metadata: bool = True,
53
+ include_function_table: bool = True,
54
+ include_text_tree: bool = True,
55
+ ) -> None:
56
+ """
57
+ Generate Mermaid diagram and save to markdown file.
58
+
59
+ Args:
60
+ result: Analysis result containing call tree
61
+ output_path: Path to output markdown file
62
+ include_metadata: Include metadata section
63
+ include_function_table: Include function details table
64
+ include_text_tree: Include text-based tree representation
65
+ """
66
+ if not result.call_tree:
67
+ raise ValueError("Cannot generate diagram: call tree is None")
68
+
69
+ # Build content sections
70
+ content = []
71
+
72
+ # Add title
73
+ content.append(f"# Call Tree: {result.root_function}\n")
74
+
75
+ # Add metadata
76
+ if include_metadata:
77
+ content.append(self._generate_metadata(result))
78
+
79
+ # Add sequence diagram
80
+ content.append("## Sequence Diagram\n")
81
+ diagram = self._generate_mermaid_diagram(result.call_tree)
82
+ content.append("```mermaid")
83
+ content.append(diagram)
84
+ content.append("```\n")
85
+
86
+ # Add function details table
87
+ if include_function_table:
88
+ content.append(self._generate_function_table(result.call_tree))
89
+
90
+ # Add text tree
91
+ if include_text_tree:
92
+ content.append(self._generate_text_tree(result.call_tree))
93
+
94
+ # Add circular dependencies if any
95
+ if result.circular_dependencies:
96
+ content.append(self._generate_circular_deps_section(result))
97
+
98
+ # Write to file
99
+ output_file = Path(output_path)
100
+ output_file.parent.mkdir(parents=True, exist_ok=True)
101
+ output_file.write_text("\n".join(content), encoding="utf-8")
102
+
103
+ def _generate_metadata(self, result: AnalysisResult) -> str:
104
+ """
105
+ Generate metadata section.
106
+
107
+ Args:
108
+ result: Analysis result
109
+
110
+ Returns:
111
+ Markdown formatted metadata
112
+ """
113
+ lines = [
114
+ "## Metadata\n",
115
+ f"- **Root Function**: `{result.root_function}`",
116
+ f"- **Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
117
+ f"- **Total Functions**: {result.statistics.total_functions}",
118
+ f"- **Unique Functions**: {result.statistics.unique_functions}",
119
+ f"- **Max Depth**: {result.statistics.max_depth_reached}",
120
+ f"- **Circular Dependencies**: {result.statistics.circular_dependencies_found}",
121
+ "",
122
+ ]
123
+ return "\n".join(lines)
124
+
125
+ def _generate_mermaid_diagram(self, root: CallTreeNode) -> str:
126
+ """
127
+ Generate Mermaid sequence diagram syntax.
128
+
129
+ Args:
130
+ root: Root node of call tree
131
+
132
+ Returns:
133
+ Mermaid diagram as string
134
+ """
135
+ lines = ["sequenceDiagram"]
136
+
137
+ # Collect all participants
138
+ participants = self._collect_participants(root)
139
+
140
+ # Add participant declarations
141
+ for participant in participants:
142
+ if self.abbreviate_rte and participant.startswith("Rte_"):
143
+ abbrev = self._abbreviate_rte_name(participant)
144
+ self.participant_map[participant] = abbrev
145
+ lines.append(f" participant {abbrev} as {participant}")
146
+ else:
147
+ lines.append(f" participant {participant}")
148
+
149
+ lines.append("")
150
+
151
+ # Generate sequence calls
152
+ self._generate_sequence_calls(root, lines)
153
+
154
+ return "\n".join(lines)
155
+
156
+ def _collect_participants(self, root: CallTreeNode) -> List[str]:
157
+ """
158
+ Collect all unique participants (functions or modules) in tree.
159
+
160
+ Implements: SWR_MERMAID_00001 (Module-Based Participants)
161
+
162
+ Args:
163
+ root: Root node of call tree
164
+
165
+ Returns:
166
+ List of participant names in the order they are first encountered
167
+ """
168
+ participants = []
169
+
170
+ def traverse(node: CallTreeNode):
171
+ # Use module name if enabled, otherwise use function name
172
+ if self.use_module_names:
173
+ # Use module name if available, otherwise fallback to filename
174
+ participant = (
175
+ node.function_info.sw_module
176
+ or Path(node.function_info.file_path).stem
177
+ )
178
+ else:
179
+ participant = node.function_info.name
180
+
181
+ # Add participant only if not already in the list
182
+ if participant not in participants:
183
+ participants.append(participant)
184
+
185
+ for child in node.children:
186
+ traverse(child)
187
+
188
+ traverse(root)
189
+ return participants
190
+
191
+ def _generate_sequence_calls(
192
+ self, node: CallTreeNode, lines: List[str], caller: Optional[str] = None
193
+ ) -> None:
194
+ """
195
+ Generate sequence call statements recursively.
196
+
197
+ Implements: SWR_MERMAID_00001 (Module-Based Participants with function names on arrows)
198
+
199
+ Args:
200
+ node: Current node in call tree
201
+ lines: List of lines to append to
202
+ caller: Name of calling function or module (None for root)
203
+ """
204
+ # Determine current participant (module or function name)
205
+ if self.use_module_names:
206
+ current_participant = (
207
+ node.function_info.sw_module or Path(node.function_info.file_path).stem
208
+ )
209
+ # When using modules, show function name on arrows
210
+ call_label = node.function_info.name
211
+ else:
212
+ current_participant = self._get_participant_name(node.function_info.name)
213
+ # When using function names, show generic "call" label
214
+ call_label = "call"
215
+
216
+ # Add parameters to the call label
217
+ if node.function_info.parameters:
218
+ params_str = self._format_parameters_for_diagram(node.function_info)
219
+ call_label = f"{call_label}({params_str})"
220
+
221
+ # Generate call from caller to current
222
+ if caller:
223
+ if node.is_recursive:
224
+ if self.use_module_names:
225
+ label = f"{call_label} [recursive]"
226
+ else:
227
+ label = "recursive call"
228
+ lines.append(f" {caller}-->>x{current_participant}: {label}")
229
+ else:
230
+ lines.append(f" {caller}->>{current_participant}: {call_label}")
231
+
232
+ # Generate calls to children
233
+ for child in node.children:
234
+ self._generate_sequence_calls(child, lines, current_participant)
235
+
236
+ # Generate return from current to caller (only if include_returns is True)
237
+ if caller and not node.is_recursive and self.include_returns:
238
+ lines.append(f" {current_participant}-->>{caller}: return")
239
+
240
+ def _get_participant_name(self, function_name: str) -> str:
241
+ """
242
+ Get participant name (possibly abbreviated).
243
+
244
+ Args:
245
+ function_name: Original function name
246
+
247
+ Returns:
248
+ Participant name to use in diagram
249
+ """
250
+ return str(self.participant_map.get(function_name, function_name))
251
+
252
+ def _abbreviate_rte_name(self, rte_function: str) -> str:
253
+ """
254
+ Abbreviate RTE function name.
255
+
256
+ Args:
257
+ rte_function: Full RTE function name
258
+
259
+ Returns:
260
+ Abbreviated name
261
+ """
262
+ # Simple abbreviation: Rte_Read_P_Voltage_Value -> Rte_Read_PVV
263
+ parts = rte_function.split("_")
264
+ if len(parts) <= 2:
265
+ return rte_function
266
+
267
+ # Keep Rte_ prefix and first operation, abbreviate rest
268
+ prefix = "_".join(parts[:2]) # e.g., "Rte_Read"
269
+ abbrev_parts = [p[0].upper() for p in parts[2:] if p]
270
+ abbrev = "".join(abbrev_parts)
271
+
272
+ return f"{prefix}_{abbrev}"
273
+
274
+ def _generate_function_table(self, root: CallTreeNode) -> str:
275
+ """
276
+ Generate markdown table of function details.
277
+
278
+ Args:
279
+ root: Root node of call tree
280
+
281
+ Returns:
282
+ Markdown formatted table
283
+ """
284
+ # Build header based on whether we're showing modules
285
+ if self.use_module_names:
286
+ lines = [
287
+ "## Function Details\n",
288
+ "| Function | Module | File | Line | Return Type | Parameters |",
289
+ "|----------|--------|------|------|-------------|------------|",
290
+ ]
291
+ else:
292
+ lines = [
293
+ "## Function Details\n",
294
+ "| Function | File | Line | Return Type | Parameters |",
295
+ "|----------|------|------|-------------|------------|",
296
+ ]
297
+
298
+ # Collect all unique functions
299
+ functions = []
300
+ seen = set()
301
+
302
+ def traverse(node: CallTreeNode):
303
+ if node.function_info.name not in seen:
304
+ seen.add(node.function_info.name)
305
+ functions.append(node.function_info)
306
+ for child in node.children:
307
+ traverse(child)
308
+
309
+ traverse(root)
310
+
311
+ # Sort by function name
312
+ functions.sort(key=lambda f: f.name)
313
+
314
+ # Add table rows
315
+ for func in functions:
316
+ file_name = Path(func.file_path).name
317
+ params = self._format_parameters(func)
318
+
319
+ if self.use_module_names:
320
+ module = func.sw_module or "N/A"
321
+ lines.append(
322
+ f"| `{func.name}` | {module} | {file_name} | {func.line_number} | "
323
+ f"`{func.return_type}` | {params} |"
324
+ )
325
+ else:
326
+ lines.append(
327
+ f"| `{func.name}` | {file_name} | {func.line_number} | "
328
+ f"`{func.return_type}` | {params} |"
329
+ )
330
+
331
+ lines.append("")
332
+ return "\n".join(lines)
333
+
334
+ def _format_parameters(self, func: FunctionInfo) -> str:
335
+ """
336
+ Format function parameters for table.
337
+
338
+ Args:
339
+ func: Function information
340
+
341
+ Returns:
342
+ Formatted parameter string
343
+ """
344
+ if not func.parameters:
345
+ return "`void`"
346
+
347
+ param_strs = []
348
+ for param in func.parameters:
349
+ type_str = param.param_type
350
+ if param.is_pointer:
351
+ type_str += "*"
352
+
353
+ if param.name:
354
+ param_strs.append(f"`{type_str} {param.name}`")
355
+ else:
356
+ param_strs.append(f"`{type_str}`")
357
+
358
+ return "<br>".join(param_strs)
359
+
360
+ def _format_parameters_for_diagram(self, func: FunctionInfo) -> str:
361
+ """
362
+ Format function parameters for sequence diagram display.
363
+
364
+ Args:
365
+ func: Function information
366
+
367
+ Returns:
368
+ Formatted parameter string for diagram
369
+ """
370
+ if not func.parameters:
371
+ return ""
372
+
373
+ param_strs = []
374
+ for param in func.parameters:
375
+ if param.name:
376
+ param_strs.append(param.name)
377
+ else:
378
+ # If no parameter name, use the type
379
+ type_str = param.param_type
380
+ if param.is_pointer:
381
+ type_str += "*"
382
+ param_strs.append(type_str)
383
+
384
+ return ", ".join(param_strs)
385
+
386
+ def _generate_text_tree(self, root: CallTreeNode) -> str:
387
+ """
388
+ Generate text-based tree representation.
389
+
390
+ Args:
391
+ root: Root node of call tree
392
+
393
+ Returns:
394
+ Markdown formatted text tree
395
+ """
396
+ lines = ["## Call Tree (Text)\n", "```"]
397
+
398
+ def traverse(node: CallTreeNode, prefix: str = "", is_last: bool = True):
399
+ connector = "└── " if is_last else "├── "
400
+
401
+ func_name = node.function_info.name
402
+ file_name = Path(node.function_info.file_path).name
403
+ line = f"{prefix}{connector}{func_name} ({file_name}:{node.function_info.line_number})"
404
+
405
+ if node.is_recursive:
406
+ line += " [RECURSIVE]"
407
+
408
+ lines.append(line)
409
+
410
+ if node.children:
411
+ new_prefix = prefix + (" " if is_last else "│ ")
412
+ for idx, child in enumerate(node.children):
413
+ is_last_child = idx == len(node.children) - 1
414
+ traverse(child, new_prefix, is_last_child)
415
+
416
+ # Start with root
417
+ func_name = root.function_info.name
418
+ file_name = Path(root.function_info.file_path).name
419
+ lines.append(f"{func_name} ({file_name}:{root.function_info.line_number})")
420
+
421
+ for idx, child in enumerate(root.children):
422
+ is_last = idx == len(root.children) - 1
423
+ traverse(child, "", is_last)
424
+
425
+ lines.append("```\n")
426
+ return "\n".join(lines)
427
+
428
+ def _generate_circular_deps_section(self, result: AnalysisResult) -> str:
429
+ """
430
+ Generate section for circular dependencies.
431
+
432
+ Args:
433
+ result: Analysis result
434
+
435
+ Returns:
436
+ Markdown formatted circular dependencies section
437
+ """
438
+ lines = [
439
+ "## Circular Dependencies\n",
440
+ f"Found {len(result.circular_dependencies)} circular dependencies:\n",
441
+ ]
442
+
443
+ for idx, circ_dep in enumerate(result.circular_dependencies, 1):
444
+ cycle_str = " → ".join(circ_dep.cycle)
445
+ lines.append(f"{idx}. **Depth {circ_dep.depth}**: `{cycle_str}`")
446
+
447
+ lines.append("")
448
+ return "\n".join(lines)
449
+
450
+ def generate_to_string(self, result: AnalysisResult) -> str:
451
+ """
452
+ Generate Mermaid diagram as string without writing to file.
453
+
454
+ Args:
455
+ result: Analysis result
456
+
457
+ Returns:
458
+ Complete markdown document as string
459
+ """
460
+ if not result.call_tree:
461
+ raise ValueError("Cannot generate diagram: call tree is None")
462
+
463
+ content = []
464
+
465
+ # Add title
466
+ content.append(f"# Call Tree: {result.root_function}\n")
467
+
468
+ # Add metadata
469
+ content.append(self._generate_metadata(result))
470
+
471
+ # Add sequence diagram
472
+ content.append("## Sequence Diagram\n")
473
+ diagram = self._generate_mermaid_diagram(result.call_tree)
474
+ content.append("```mermaid")
475
+ content.append(diagram)
476
+ content.append("```\n")
477
+
478
+ # Add function table
479
+ content.append(self._generate_function_table(result.call_tree))
480
+
481
+ # Add text tree
482
+ content.append(self._generate_text_tree(result.call_tree))
483
+
484
+ # Add circular dependencies
485
+ if result.circular_dependencies:
486
+ content.append(self._generate_circular_deps_section(result))
487
+
488
+ return "\n".join(content)
@@ -0,0 +1,6 @@
1
+ """Parsers package initialization."""
2
+
3
+ from .autosar_parser import AutosarParser
4
+ from .c_parser import CParser
5
+
6
+ __all__ = ["AutosarParser", "CParser"]