autosar-calltree 0.3.2__py3-none-any.whl → 0.4.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.
@@ -17,6 +17,7 @@ from ..analyzers.call_tree_builder import CallTreeBuilder
17
17
  from ..config.module_config import ModuleConfig
18
18
  from ..database.function_database import FunctionDatabase
19
19
  from ..generators.mermaid_generator import MermaidGenerator
20
+ from ..generators.xmi_generator import XmiGenerator
20
21
  from ..version import __version__
21
22
 
22
23
  console = Console(record=True)
@@ -295,11 +296,47 @@ def cli(
295
296
  )
296
297
 
297
298
  if format == "xmi":
298
- console.print("[yellow]Warning:[/yellow] XMI format not yet implemented")
299
+ with Progress(
300
+ SpinnerColumn(),
301
+ TextColumn("[progress.description]{task.description}"),
302
+ console=console,
303
+ transient=True,
304
+ ) as progress:
305
+ task = progress.add_task("Generating XMI document...", total=None)
306
+
307
+ xmi_output = output_path.with_suffix(".xmi") if output_path.suffix != ".xmi" else output_path
308
+
309
+ xmi_generator = XmiGenerator(
310
+ use_module_names=use_module_names,
311
+ )
312
+ xmi_generator.generate(result, str(xmi_output))
313
+
314
+ progress.update(task, completed=True)
315
+
316
+ console.print(
317
+ f"[green]Generated[/green] XMI document: [cyan]{xmi_output}[/cyan]"
318
+ )
299
319
 
300
320
  if format == "both":
321
+ with Progress(
322
+ SpinnerColumn(),
323
+ TextColumn("[progress.description]{task.description}"),
324
+ console=console,
325
+ transient=True,
326
+ ) as progress:
327
+ task = progress.add_task("Generating XMI document...", total=None)
328
+
329
+ xmi_output = output_path.with_suffix(".xmi")
330
+
331
+ xmi_generator = XmiGenerator(
332
+ use_module_names=use_module_names,
333
+ )
334
+ xmi_generator.generate(result, str(xmi_output))
335
+
336
+ progress.update(task, completed=True)
337
+
301
338
  console.print(
302
- "[yellow]Warning:[/yellow] XMI format not yet implemented (only Mermaid generated)"
339
+ f"[green]Generated[/green] XMI document: [cyan]{xmi_output}[/cyan]"
303
340
  )
304
341
 
305
342
  # Print warnings for circular dependencies
@@ -1,5 +1,6 @@
1
1
  """Generators package initialization."""
2
2
 
3
3
  from .mermaid_generator import MermaidGenerator
4
+ from .xmi_generator import XmiGenerator
4
5
 
5
- __all__ = ["MermaidGenerator"]
6
+ __all__ = ["MermaidGenerator", "XmiGenerator"]
@@ -0,0 +1,357 @@
1
+ """
2
+ XMI (XML Metadata Interchange) generator for UML sequence diagrams.
3
+
4
+ This module generates XMI 2.5 compliant XML documents representing
5
+ UML sequence diagrams from call trees. The XMI format can be imported
6
+ into UML tools like Enterprise Architect, Visual Paradigm, etc.
7
+
8
+ Requirements:
9
+ - SWR_XMI_00001: XMI 2.5 Compliance
10
+ - SWR_XMI_00002: Sequence Diagram Representation
11
+ """
12
+
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional
15
+ from xml.dom import minidom
16
+ from xml.etree.ElementTree import Element, SubElement, tostring
17
+
18
+ from ..database.models import AnalysisResult, CallTreeNode
19
+
20
+
21
+ class XmiGenerator:
22
+ """
23
+ Generates XMI 2.5 format UML sequence diagrams from call trees.
24
+
25
+ This class converts call tree structures into XMI XML documents,
26
+ following the UML 2.5 XMI specification for sequence diagrams.
27
+ """
28
+
29
+ # XMI/UML namespaces
30
+ XMI_NAMESPACE = "http://www.omg.org/spec/XMI/20131001"
31
+ XMI_URI = "http://www.omg.org/spec/XMI/20131001"
32
+ UML_NAMESPACE = "http://www.eclipse.org/uml2/5.0.0/UML"
33
+ UML_URI = "http://www.eclipse.org/uml2/5.0.0/UML"
34
+
35
+ def __init__(
36
+ self,
37
+ use_module_names: bool = False,
38
+ ):
39
+ """
40
+ Initialize the XMI generator.
41
+
42
+ Args:
43
+ use_module_names: Use SW module names as participants instead of function names
44
+ """
45
+ self.use_module_names = use_module_names
46
+ self.element_id_counter = 0
47
+ self.participant_map: Dict[str, str] = {} # Map names to XMI IDs
48
+
49
+ def generate(
50
+ self,
51
+ result: AnalysisResult,
52
+ output_path: str,
53
+ ) -> None:
54
+ """
55
+ Generate XMI document and save to file.
56
+
57
+ Args:
58
+ result: Analysis result containing call tree
59
+ output_path: Path to output XMI file
60
+ """
61
+ if not result.call_tree:
62
+ raise ValueError("Cannot generate XMI: call tree is None")
63
+
64
+ # Build XMI document
65
+ root_element = self._generate_xmi_document(result, result.call_tree)
66
+
67
+ # Pretty print and write to file
68
+ xml_str = self._prettify_xml(root_element)
69
+ output_file = Path(output_path)
70
+ output_file.parent.mkdir(parents=True, exist_ok=True)
71
+ output_file.write_text(xml_str, encoding="utf-8")
72
+
73
+ def _generate_xmi_document(self, result: AnalysisResult, call_tree: CallTreeNode) -> Element:
74
+ """
75
+ Generate XMI root element with complete document structure.
76
+
77
+ Args:
78
+ result: Analysis result containing metadata
79
+ call_tree: Root node of the call tree (non-optional)
80
+
81
+ Returns:
82
+ Root XML Element for the XMI document
83
+ """
84
+ # Create root element with XMI namespace
85
+ root = Element(
86
+ f"{{{self.XMI_NAMESPACE}}}XMI",
87
+ attrib={
88
+ f"{{{self.XMI_NAMESPACE}}}version": "4.0",
89
+ "xmlns:xmi": self.XMI_URI,
90
+ "xmlns:uml": self.UML_URI,
91
+ },
92
+ )
93
+
94
+ # Create UML model
95
+ model = SubElement(root, f"{{{self.UML_NAMESPACE}}}Model", attrib={
96
+ f"{{{self.XMI_NAMESPACE}}}id": self._generate_id(),
97
+ "name": f"CallTree_{result.root_function}",
98
+ "visibility": "public",
99
+ })
100
+
101
+ # Package the model
102
+ packaged_element = SubElement(model, f"{{{self.UML_NAMESPACE}}}packagedElement")
103
+ packaged_element.set(f"{{{self.XMI_NAMESPACE}}}id", self._generate_id())
104
+ packaged_element.set("name", f"Sequence_{result.root_function}")
105
+ packaged_element.set("visibility", "public")
106
+
107
+ # Create interaction (sequence diagram container)
108
+ interaction = self._create_interaction(packaged_element, result)
109
+
110
+ # Collect all participants and create lifelines
111
+ participants = self._collect_participants(call_tree)
112
+ lifeline_elements = self._create_lifelines(interaction, participants)
113
+
114
+ # Create messages (function calls)
115
+ self._create_messages(call_tree, lifeline_elements, interaction)
116
+
117
+ return root
118
+
119
+ def _create_interaction(
120
+ self, parent: Element, result: AnalysisResult
121
+ ) -> Element:
122
+ """
123
+ Create UML interaction element (sequence diagram container).
124
+
125
+ Args:
126
+ parent: Parent element
127
+ result: Analysis result
128
+
129
+ Returns:
130
+ Interaction element
131
+ """
132
+ interaction = SubElement(parent, f"{{{self.UML_NAMESPACE}}}packagedElement")
133
+ interaction.set(f"{{{self.XMI_NAMESPACE}}}id", self._generate_id())
134
+ interaction.set("name", f"CallSequence_{result.root_function}")
135
+ interaction.set("visibility", "public")
136
+ interaction.set("isReentrant", "false")
137
+
138
+ # Set type to Interaction
139
+ interaction_type = SubElement(interaction, f"{{{self.UML_NAMESPACE}}}ownedAttribute")
140
+ interaction_type.set(f"{{{self.XMI_NAMESPACE}}}id", self._generate_id())
141
+ interaction_type.set("name", "interactionType")
142
+ interaction_type.set("visibility", "public")
143
+ interaction_type.set("type", "Interaction")
144
+
145
+ return interaction
146
+
147
+ def _collect_participants(self, root: CallTreeNode) -> List[str]:
148
+ """
149
+ Collect all unique participants (functions or modules) in tree.
150
+
151
+ Args:
152
+ root: Root node of call tree
153
+
154
+ Returns:
155
+ List of participant names in the order they are first encountered
156
+ """
157
+ participants = []
158
+
159
+ def traverse(node: CallTreeNode):
160
+ # Use module name if enabled, otherwise use function name
161
+ if self.use_module_names:
162
+ participant = (
163
+ node.function_info.sw_module
164
+ or Path(node.function_info.file_path).stem
165
+ )
166
+ else:
167
+ participant = node.function_info.name
168
+
169
+ # Add participant only if not already in the list
170
+ if participant not in participants:
171
+ participants.append(participant)
172
+
173
+ for child in node.children:
174
+ traverse(child)
175
+
176
+ traverse(root)
177
+ return participants
178
+
179
+ def _create_lifelines(
180
+ self, interaction: Element, participants: List[str]
181
+ ) -> Dict[str, Element]:
182
+ """
183
+ Create UML lifelines for each participant.
184
+
185
+ Args:
186
+ interaction: Interaction element
187
+ participants: List of participant names
188
+
189
+ Returns:
190
+ Dictionary mapping participant names to their lifeline elements
191
+ """
192
+ lifeline_elements = {}
193
+
194
+ for idx, participant in enumerate(participants):
195
+ # Create lifeline
196
+ lifeline = SubElement(interaction, f"{{{self.UML_NAMESPACE}}}lifeline")
197
+ lifeline_id = self._generate_id()
198
+ lifeline.set(f"{{{self.XMI_NAMESPACE}}}id", lifeline_id)
199
+ lifeline.set("name", participant)
200
+ lifeline.set("visibility", "public")
201
+
202
+ # Create represents property (connects to classifier)
203
+ represents = SubElement(lifeline, f"{{{self.UML_NAMESPACE}}}represents")
204
+ represents_id = self._generate_id()
205
+ represents.set(f"{{{self.XMI_NAMESPACE}}}id", represents_id)
206
+ represents.set("name", participant)
207
+
208
+ # Store mapping
209
+ self.participant_map[participant] = lifeline_id
210
+ lifeline_elements[participant] = lifeline
211
+
212
+ return lifeline_elements
213
+
214
+ def _create_messages(
215
+ self,
216
+ root: CallTreeNode,
217
+ lifeline_elements: Dict[str, Element],
218
+ interaction: Element,
219
+ ) -> None:
220
+ """
221
+ Create UML messages (function calls) between lifelines.
222
+
223
+ Args:
224
+ root: Root node of call tree
225
+ lifeline_elements: Dictionary of lifeline elements
226
+ interaction: Interaction element
227
+ """
228
+ message_counter = 0
229
+
230
+ def traverse(node: CallTreeNode, parent_participant: Optional[str] = None):
231
+ nonlocal message_counter
232
+
233
+ # Determine current participant
234
+ if self.use_module_names:
235
+ current_participant = (
236
+ node.function_info.sw_module
237
+ or Path(node.function_info.file_path).stem
238
+ )
239
+ else:
240
+ current_participant = node.function_info.name
241
+
242
+ # Create message from parent to current
243
+ if parent_participant:
244
+ message = SubElement(interaction, f"{{{self.UML_NAMESPACE}}}message")
245
+ message_id = self._generate_id()
246
+ message.set(f"{{{self.XMI_NAMESPACE}}}id", message_id)
247
+ message.set("name", node.function_info.name)
248
+ message.set("visibility", "public")
249
+ message.set("messageSort", "synchCall")
250
+
251
+ # Set message signature
252
+ signature = self._format_message_signature(node)
253
+ message.set("signature", signature)
254
+
255
+ # Connect to lifelines
256
+ send_event = SubElement(message, f"{{{self.UML_NAMESPACE}}}sendEvent")
257
+ send_event.set(f"{{{self.XMI_NAMESPACE}}}id", self._generate_id())
258
+
259
+ receive_event = SubElement(message, f"{{{self.UML_NAMESPACE}}}receiveEvent")
260
+ receive_event.set(f"{{{self.XMI_NAMESPACE}}}id", self._generate_id())
261
+
262
+ # Set source and target
263
+ message.set(
264
+ "sendEvent",
265
+ f"{{{self.XMI_NAMESPACE}}}{self.participant_map.get(parent_participant, '')}"
266
+ )
267
+ message.set(
268
+ "receiveEvent",
269
+ f"{{{self.XMI_NAMESPACE}}}{self.participant_map.get(current_participant, '')}"
270
+ )
271
+
272
+ message_counter += 1
273
+
274
+ # Mark recursive calls
275
+ if node.is_recursive:
276
+ message.set("messageSort", "reply")
277
+
278
+ # Traverse children
279
+ for child in node.children:
280
+ traverse(child, current_participant)
281
+
282
+ # Start traversal from root's children
283
+ for child in root.children:
284
+ traverse(child, None)
285
+
286
+ def _format_message_signature(self, node: CallTreeNode) -> str:
287
+ """
288
+ Format message signature with parameters.
289
+
290
+ Args:
291
+ node: Call tree node
292
+
293
+ Returns:
294
+ Formatted signature string
295
+ """
296
+ func = node.function_info
297
+
298
+ if not func.parameters:
299
+ return f"{func.name}()"
300
+
301
+ params = []
302
+ for param in func.parameters:
303
+ if param.name:
304
+ params.append(param.name)
305
+ else:
306
+ type_str = param.param_type
307
+ if param.is_pointer:
308
+ type_str += "*"
309
+ params.append(type_str)
310
+
311
+ return f"{func.name}({', '.join(params)})"
312
+
313
+ def _generate_id(self) -> str:
314
+ """
315
+ Generate unique XMI ID.
316
+
317
+ Returns:
318
+ Unique identifier string
319
+ """
320
+ self.element_id_counter += 1
321
+ return f"calltree_{self.element_id_counter}"
322
+
323
+ def _prettify_xml(self, element: Element) -> str:
324
+ """
325
+ Convert XML element to pretty-printed string.
326
+
327
+ Args:
328
+ element: Root XML element
329
+
330
+ Returns:
331
+ Pretty-printed XML string
332
+ """
333
+ rough_string = tostring(element, encoding="unicode")
334
+ reparsed = minidom.parseString(rough_string)
335
+ pretty_xml = reparsed.toprettyxml(indent=" ")
336
+
337
+ # Add XML declaration and comments
338
+ lines = pretty_xml.split('\n')
339
+ filtered_lines = [line for line in lines if line.strip()]
340
+
341
+ return '\n'.join(filtered_lines)
342
+
343
+ def generate_to_string(self, result: AnalysisResult) -> str:
344
+ """
345
+ Generate XMI document as string without writing to file.
346
+
347
+ Args:
348
+ result: Analysis result
349
+
350
+ Returns:
351
+ XMI document as string
352
+ """
353
+ if not result.call_tree:
354
+ raise ValueError("Cannot generate XMI: call tree is None")
355
+
356
+ root_element = self._generate_xmi_document(result, result.call_tree)
357
+ return self._prettify_xml(root_element)
@@ -58,6 +58,29 @@ class CParser:
58
58
  "_Generic",
59
59
  }
60
60
 
61
+ # AUTOSAR and standard C macros to exclude from function detection
62
+ # These macros look like function calls but should not be parsed as functions
63
+ AUTOSAR_MACROS = {
64
+ # Standard C integer literal macros (stdint.h)
65
+ "INT8_C",
66
+ "INT16_C",
67
+ "INT32_C",
68
+ "INT64_C",
69
+ "UINT8_C",
70
+ "UINT16_C",
71
+ "UINT32_C",
72
+ "UINT64_C",
73
+ "INTMAX_C",
74
+ "UINTMAX_C",
75
+ # AUTOSAR tool-specific macros
76
+ "TS_MAKEREF2CFG",
77
+ "TS_MAKENULLREF2CFG",
78
+ "TS_MAKEREFLIST2CFG",
79
+ # Common AUTOSAR configuration macros
80
+ "STD_ON",
81
+ "STD_OFF",
82
+ }
83
+
61
84
  # Common AUTOSAR types
62
85
  AUTOSAR_TYPES = {
63
86
  "uint8",
@@ -244,6 +267,16 @@ class CParser:
244
267
  if return_type in self.C_KEYWORDS or function_name in self.C_KEYWORDS:
245
268
  return None
246
269
 
270
+ # Skip AUTOSAR and standard C macros that look like function calls
271
+ # This prevents false positives on macros like UINT32_C(value), TS_MAKEREF2CFG(...)
272
+ if function_name in self.AUTOSAR_MACROS:
273
+ return None
274
+
275
+ # Skip standard C integer literal macros (those ending with _C)
276
+ # These are defined in stdint.h and look like: INT32_C(42), UINT64_C(100)
277
+ if function_name.endswith("_C"):
278
+ return None
279
+
247
280
  # Skip common control structures
248
281
  if function_name in ["if", "for", "while", "switch", "case", "else"]:
249
282
  return None
@@ -1,5 +1,5 @@
1
1
  """Version information for autosar-calltree package."""
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.4.0"
4
4
  __author__ = "melodypapa"
5
5
  __email__ = "melodypapa@outlook.com"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autosar-calltree
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: A Python tool to analyze C/AUTOSAR codebases and generate function call trees with Mermaid and XMI output
5
5
  Author-email: melodypapa <melodypapa@outlook.com>
6
6
  Maintainer-email: melodypapa <melodypapa@outlook.com>
@@ -255,7 +255,7 @@ default_module: "Other"
255
255
 
256
256
  **Usage**:
257
257
  ```bash
258
- calltree --start-function Demo_Init --module-config module_mapping.yaml --use-module-names --max-depth 3
258
+ calltree --start-function Demo_Init --module-config demo/module_mapping.yaml --use-module-names --max-depth 3 --output demo/demo_sequence.md
259
259
  ```
260
260
 
261
261
  This generates diagrams with:
@@ -1,22 +1,23 @@
1
1
  autosar_calltree/__init__.py,sha256=WiP2aFfs0H3ze4X8tQg6ZiStdzgrp6uC8DPRUyGm_zg,660
2
- autosar_calltree/version.py,sha256=zDB6LGdYGRR-wdfWuRQEezaGHN9pJEeYwqXX2LokRZI,142
2
+ autosar_calltree/version.py,sha256=Nt6KvWNqsurDjWOhy4Xi-L1EtfOXOROb5rxxO8OE_bA,142
3
3
  autosar_calltree/analyzers/__init__.py,sha256=qUvB7CAa5xNcUYkhD7Rha-9quPG8yQJqg-GikHD41U0,119
4
4
  autosar_calltree/analyzers/call_tree_builder.py,sha256=tSsIj_BHzT4XQ-XsXXfyU-KyGK61p6O7nLBx1AiwxCA,11627
5
5
  autosar_calltree/cli/__init__.py,sha256=qwsSUuDyV6i1PYS7Mf6JBkBJKtcldxiIh5S0wOucWVY,76
6
- autosar_calltree/cli/main.py,sha256=5svIu95DykrQBtjmhQTOTe-RdM9LkIgnnUnvfvivMM8,11334
6
+ autosar_calltree/cli/main.py,sha256=f9awQW_1dKIc1IjhOp5_yx7f2CzRybNFKQt2tILwj1c,12676
7
7
  autosar_calltree/config/__init__.py,sha256=mSrB2uvrax_MTgGynfPObXYUh6eCe5BZD21_cebfMQM,258
8
8
  autosar_calltree/config/module_config.py,sha256=--ptsY6drvVKn_HD5_JDEpCv9D1xI8kiJXi8bwrSFBI,6329
9
9
  autosar_calltree/database/__init__.py,sha256=qg0IfUGgkecJQXqe9lPrU0Xbu-hBZa8NOe8E8UZqC_s,405
10
10
  autosar_calltree/database/function_database.py,sha256=PJghwv2Duxx7vSIvApyVwg4N_bCTfnk85mtA89BpIuA,17561
11
11
  autosar_calltree/database/models.py,sha256=xo85zao2LDHJMR6FeMVxopPyXxEv7jQUzeh4fzHwNKo,6265
12
- autosar_calltree/generators/__init__.py,sha256=wlSAuykXkxIjwP9H1C2iY99nbyt14Kp1Y6GxmDEDmDk,122
12
+ autosar_calltree/generators/__init__.py,sha256=5gl3dF3imJIsvfmdg0bkZdnQ9vFWCTlpMWEhz6I_IZg,178
13
13
  autosar_calltree/generators/mermaid_generator.py,sha256=LcmWOACLAw3r7UQlnSlTM-_kpKQ6RGdgc6aNqXeMF7A,16191
14
+ autosar_calltree/generators/xmi_generator.py,sha256=ndvDapHppOoDQ9q5Zu9b1AFbUKKc8q3copUti3UaFZM,12168
14
15
  autosar_calltree/parsers/__init__.py,sha256=O0NTHrZqe6GXvU9_bLuAoAV_hxv7gHOgkH8KWSBmX1Y,151
15
16
  autosar_calltree/parsers/autosar_parser.py,sha256=iwF1VR4NceEfmc3gPP31CcVNPfRRVTj_aA6N94saXDA,10750
16
- autosar_calltree/parsers/c_parser.py,sha256=OQ0HklIV1aSWvxe6qjj5bwP5Drz4cmxhFrdrOBpZdFU,14744
17
- autosar_calltree-0.3.2.dist-info/licenses/LICENSE,sha256=Xy30Wm38nOLXLZZFgn9oD_3UcayYkm81xtn8IByrBlk,1067
18
- autosar_calltree-0.3.2.dist-info/METADATA,sha256=oLQzj1T0HauxX38XVi7tHunMvO20raEydsGHKDfnFRY,15530
19
- autosar_calltree-0.3.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
20
- autosar_calltree-0.3.2.dist-info/entry_points.txt,sha256=HfntIC1V_COOhGJ-OhtLKH_2vJ1jQy5Hlz8NmcJg4TQ,54
21
- autosar_calltree-0.3.2.dist-info/top_level.txt,sha256=eusKGYzQfbhwIFDsUYTby40SdMpe95Y9GtR0l1GVIDQ,17
22
- autosar_calltree-0.3.2.dist-info/RECORD,,
17
+ autosar_calltree/parsers/c_parser.py,sha256=2LCDVZ8bcrBiQe9DK7kNMmvzhGegEDfd57KbI5JB43A,15846
18
+ autosar_calltree-0.4.0.dist-info/licenses/LICENSE,sha256=Xy30Wm38nOLXLZZFgn9oD_3UcayYkm81xtn8IByrBlk,1067
19
+ autosar_calltree-0.4.0.dist-info/METADATA,sha256=9O3LbtSX1byivDrDmCATcTSX5fT_Cxb-E6F0pyqhyIE,15566
20
+ autosar_calltree-0.4.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
21
+ autosar_calltree-0.4.0.dist-info/entry_points.txt,sha256=HfntIC1V_COOhGJ-OhtLKH_2vJ1jQy5Hlz8NmcJg4TQ,54
22
+ autosar_calltree-0.4.0.dist-info/top_level.txt,sha256=eusKGYzQfbhwIFDsUYTby40SdMpe95Y9GtR0l1GVIDQ,17
23
+ autosar_calltree-0.4.0.dist-info/RECORD,,