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.
- autosar_calltree/__init__.py +24 -0
- autosar_calltree/analyzers/__init__.py +5 -0
- autosar_calltree/analyzers/call_tree_builder.py +369 -0
- autosar_calltree/cli/__init__.py +5 -0
- autosar_calltree/cli/main.py +330 -0
- autosar_calltree/config/__init__.py +10 -0
- autosar_calltree/config/module_config.py +179 -0
- autosar_calltree/database/__init__.py +23 -0
- autosar_calltree/database/function_database.py +505 -0
- autosar_calltree/database/models.py +189 -0
- autosar_calltree/generators/__init__.py +5 -0
- autosar_calltree/generators/mermaid_generator.py +488 -0
- autosar_calltree/parsers/__init__.py +6 -0
- autosar_calltree/parsers/autosar_parser.py +314 -0
- autosar_calltree/parsers/c_parser.py +415 -0
- autosar_calltree/version.py +5 -0
- autosar_calltree-0.3.0.dist-info/METADATA +482 -0
- autosar_calltree-0.3.0.dist-info/RECORD +22 -0
- autosar_calltree-0.3.0.dist-info/WHEEL +5 -0
- autosar_calltree-0.3.0.dist-info/entry_points.txt +2 -0
- autosar_calltree-0.3.0.dist-info/licenses/LICENSE +21 -0
- autosar_calltree-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for autosar-calltree.
|
|
3
|
+
|
|
4
|
+
This module provides the main CLI entry point using Click.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from ..analyzers.call_tree_builder import CallTreeBuilder
|
|
17
|
+
from ..config.module_config import ModuleConfig
|
|
18
|
+
from ..database.function_database import FunctionDatabase
|
|
19
|
+
from ..generators.mermaid_generator import MermaidGenerator
|
|
20
|
+
from ..version import __version__
|
|
21
|
+
|
|
22
|
+
console = Console(record=True)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.option(
|
|
27
|
+
"--start-function",
|
|
28
|
+
"-s",
|
|
29
|
+
required=False, # Not required if --list-functions or --search is used
|
|
30
|
+
help="Name of the function to start call tree from",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--max-depth",
|
|
34
|
+
"-d",
|
|
35
|
+
default=3,
|
|
36
|
+
type=int,
|
|
37
|
+
help="Maximum depth to traverse (default: 3)",
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--source-dir",
|
|
41
|
+
"-i",
|
|
42
|
+
default="./demo",
|
|
43
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
|
44
|
+
help="Source directory containing C files (default: ./demo)",
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--output",
|
|
48
|
+
"-o",
|
|
49
|
+
default="call_tree.md",
|
|
50
|
+
type=click.Path(),
|
|
51
|
+
help="Output file path (default: call_tree.md)",
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--format",
|
|
55
|
+
"-f",
|
|
56
|
+
type=click.Choice(["mermaid", "xmi", "both"], case_sensitive=False),
|
|
57
|
+
default="mermaid",
|
|
58
|
+
help="Output format (default: mermaid)",
|
|
59
|
+
)
|
|
60
|
+
@click.option(
|
|
61
|
+
"--cache-dir",
|
|
62
|
+
type=click.Path(file_okay=False, dir_okay=True),
|
|
63
|
+
help="Cache directory (default: <source-dir>/.cache)",
|
|
64
|
+
)
|
|
65
|
+
@click.option("--no-cache", is_flag=True, help="Disable cache usage")
|
|
66
|
+
@click.option("--rebuild-cache", is_flag=True, help="Force rebuild of cache")
|
|
67
|
+
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
|
68
|
+
@click.option(
|
|
69
|
+
"--list-functions", "-l", is_flag=True, help="List all available functions and exit"
|
|
70
|
+
)
|
|
71
|
+
@click.option(
|
|
72
|
+
"--search", type=str, help="Search for functions matching pattern and exit"
|
|
73
|
+
)
|
|
74
|
+
@click.option(
|
|
75
|
+
"--no-abbreviate-rte",
|
|
76
|
+
is_flag=True,
|
|
77
|
+
help="Do not abbreviate RTE function names in diagrams",
|
|
78
|
+
)
|
|
79
|
+
@click.option(
|
|
80
|
+
"--module-config",
|
|
81
|
+
type=click.Path(exists=True),
|
|
82
|
+
help="Path to YAML file mapping C files to SW modules",
|
|
83
|
+
)
|
|
84
|
+
@click.option(
|
|
85
|
+
"--use-module-names",
|
|
86
|
+
is_flag=True,
|
|
87
|
+
help="Use SW module names as Mermaid participants (requires --module-config)",
|
|
88
|
+
)
|
|
89
|
+
@click.version_option(version=__version__, prog_name="autosar-calltree")
|
|
90
|
+
def cli(
|
|
91
|
+
start_function: str,
|
|
92
|
+
max_depth: int,
|
|
93
|
+
source_dir: str,
|
|
94
|
+
output: str,
|
|
95
|
+
format: str,
|
|
96
|
+
cache_dir: Optional[str],
|
|
97
|
+
no_cache: bool,
|
|
98
|
+
rebuild_cache: bool,
|
|
99
|
+
verbose: bool,
|
|
100
|
+
list_functions: bool,
|
|
101
|
+
search: Optional[str],
|
|
102
|
+
no_abbreviate_rte: bool,
|
|
103
|
+
module_config: Optional[str],
|
|
104
|
+
use_module_names: bool,
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
AUTOSAR Call Tree Analyzer
|
|
108
|
+
|
|
109
|
+
Analyzes C/AUTOSAR codebases and generates function call trees
|
|
110
|
+
with Mermaid sequence diagrams or XMI output.
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
# Print banner
|
|
114
|
+
if not verbose:
|
|
115
|
+
console.print(
|
|
116
|
+
f"[bold cyan]AUTOSAR Call Tree Analyzer v{__version__}[/bold cyan]"
|
|
117
|
+
)
|
|
118
|
+
console.print()
|
|
119
|
+
|
|
120
|
+
# Validate use_module_names requires module_config
|
|
121
|
+
if use_module_names and not module_config:
|
|
122
|
+
console.print(
|
|
123
|
+
"[yellow]Warning:[/yellow] --use-module-names requires --module-config. "
|
|
124
|
+
"Module names will not be used."
|
|
125
|
+
)
|
|
126
|
+
use_module_names = False
|
|
127
|
+
|
|
128
|
+
# Load module configuration if provided
|
|
129
|
+
config = None
|
|
130
|
+
if module_config:
|
|
131
|
+
try:
|
|
132
|
+
config = ModuleConfig(Path(module_config))
|
|
133
|
+
if verbose:
|
|
134
|
+
console.print(
|
|
135
|
+
f"[cyan]Loaded module configuration from {module_config}[/cyan]"
|
|
136
|
+
)
|
|
137
|
+
config_stats = config.get_statistics()
|
|
138
|
+
console.print(
|
|
139
|
+
f" - Specific file mappings: {config_stats['specific_file_mappings']}"
|
|
140
|
+
)
|
|
141
|
+
console.print(
|
|
142
|
+
f" - Pattern mappings: {config_stats['pattern_mappings']}"
|
|
143
|
+
)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
console.print(f"[bold red]Error loading module config:[/bold red] {e}")
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
|
|
148
|
+
# Initialize database
|
|
149
|
+
use_cache = not no_cache
|
|
150
|
+
|
|
151
|
+
with Progress(
|
|
152
|
+
SpinnerColumn(),
|
|
153
|
+
TextColumn("[progress.description]{task.description}"),
|
|
154
|
+
console=console,
|
|
155
|
+
transient=True,
|
|
156
|
+
) as progress:
|
|
157
|
+
# Build database
|
|
158
|
+
task = progress.add_task(
|
|
159
|
+
f"Building function database from {source_dir}...", total=None
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
db = FunctionDatabase(source_dir, cache_dir=cache_dir, module_config=config)
|
|
163
|
+
db.build_database(
|
|
164
|
+
use_cache=use_cache, rebuild_cache=rebuild_cache, verbose=verbose
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
progress.update(task, completed=True)
|
|
168
|
+
|
|
169
|
+
# Print statistics
|
|
170
|
+
stats = db.get_statistics()
|
|
171
|
+
|
|
172
|
+
if verbose:
|
|
173
|
+
console.print("\n[bold]Database Statistics:[/bold]")
|
|
174
|
+
table = Table(show_header=False)
|
|
175
|
+
table.add_column("Property", style="cyan")
|
|
176
|
+
table.add_column("Value", style="green")
|
|
177
|
+
table.add_row("Files Scanned", str(stats["total_files_scanned"]))
|
|
178
|
+
table.add_row("Functions Found", str(stats["total_functions_found"]))
|
|
179
|
+
table.add_row("Unique Names", str(stats["unique_function_names"]))
|
|
180
|
+
table.add_row("Static Functions", str(stats["static_functions"]))
|
|
181
|
+
table.add_row("Parse Errors", str(stats["parse_errors"]))
|
|
182
|
+
console.print(table)
|
|
183
|
+
|
|
184
|
+
# Print module statistics if available
|
|
185
|
+
if stats.get("module_stats"):
|
|
186
|
+
console.print("\n[bold]Module Distribution:[/bold]")
|
|
187
|
+
for module, count in sorted(stats["module_stats"].items()):
|
|
188
|
+
console.print(f" {module}: {count} functions")
|
|
189
|
+
console.print()
|
|
190
|
+
|
|
191
|
+
# Handle list functions
|
|
192
|
+
if list_functions:
|
|
193
|
+
console.print("[bold]Available Functions:[/bold]\n")
|
|
194
|
+
functions = db.get_all_function_names()
|
|
195
|
+
for idx, func_name in enumerate(functions, 1):
|
|
196
|
+
console.print(f"{idx:4d}. {func_name}")
|
|
197
|
+
console.print(f"\n[cyan]Total: {len(functions)} functions[/cyan]")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
# Handle search
|
|
201
|
+
if search:
|
|
202
|
+
console.print(f"[bold]Search Results for '{search}':[/bold]\n")
|
|
203
|
+
results = db.search_functions(search)
|
|
204
|
+
if results:
|
|
205
|
+
for func_info in results:
|
|
206
|
+
file_name = Path(func_info.file_path).name
|
|
207
|
+
console.print(
|
|
208
|
+
f" [cyan]{func_info.name}[/cyan] "
|
|
209
|
+
f"({file_name}:{func_info.line_number})"
|
|
210
|
+
)
|
|
211
|
+
console.print(f"\n[cyan]Found {len(results)} matches[/cyan]")
|
|
212
|
+
else:
|
|
213
|
+
console.print(
|
|
214
|
+
f"[yellow]No functions found matching '{search}'[/yellow]"
|
|
215
|
+
)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
# Validate start_function is provided if not using list/search
|
|
219
|
+
if not start_function:
|
|
220
|
+
console.print("[bold red]Error:[/bold red] --start-function is required")
|
|
221
|
+
console.print("Use --list-functions to see available functions")
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
|
|
224
|
+
# Build call tree
|
|
225
|
+
with Progress(
|
|
226
|
+
SpinnerColumn(),
|
|
227
|
+
TextColumn("[progress.description]{task.description}"),
|
|
228
|
+
console=console,
|
|
229
|
+
transient=True,
|
|
230
|
+
) as progress:
|
|
231
|
+
task = progress.add_task(
|
|
232
|
+
f"Building call tree for {start_function}...", total=None
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
builder = CallTreeBuilder(db)
|
|
236
|
+
result = builder.build_tree(
|
|
237
|
+
start_function=start_function, max_depth=max_depth, verbose=verbose
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
progress.update(task, completed=True)
|
|
241
|
+
|
|
242
|
+
# Check for errors
|
|
243
|
+
if result.errors:
|
|
244
|
+
console.print("[bold red]Errors:[/bold red]")
|
|
245
|
+
for error in result.errors:
|
|
246
|
+
console.print(f" - {error}")
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
|
|
249
|
+
# Print analysis statistics
|
|
250
|
+
if not verbose:
|
|
251
|
+
console.print("[bold]Analysis Results:[/bold]")
|
|
252
|
+
console.print(
|
|
253
|
+
f" - Total functions: [cyan]{result.statistics.total_functions}[/cyan]"
|
|
254
|
+
)
|
|
255
|
+
console.print(
|
|
256
|
+
f" - Unique functions: [cyan]{result.statistics.unique_functions}[/cyan]"
|
|
257
|
+
)
|
|
258
|
+
console.print(
|
|
259
|
+
f" - Max depth: [cyan]{result.statistics.max_depth_reached}[/cyan]"
|
|
260
|
+
)
|
|
261
|
+
if result.statistics.circular_dependencies_found > 0:
|
|
262
|
+
console.print(
|
|
263
|
+
f" - Circular dependencies: "
|
|
264
|
+
f"[yellow]{result.statistics.circular_dependencies_found}[/yellow]"
|
|
265
|
+
)
|
|
266
|
+
console.print()
|
|
267
|
+
|
|
268
|
+
# Generate output
|
|
269
|
+
output_path = Path(output)
|
|
270
|
+
|
|
271
|
+
if format in ["mermaid", "both"]:
|
|
272
|
+
with Progress(
|
|
273
|
+
SpinnerColumn(),
|
|
274
|
+
TextColumn("[progress.description]{task.description}"),
|
|
275
|
+
console=console,
|
|
276
|
+
transient=True,
|
|
277
|
+
) as progress:
|
|
278
|
+
task = progress.add_task("Generating Mermaid diagram...", total=None)
|
|
279
|
+
|
|
280
|
+
mermaid_output = (
|
|
281
|
+
output_path
|
|
282
|
+
if format == "mermaid"
|
|
283
|
+
else output_path.with_suffix(".mermaid.md")
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
generator = MermaidGenerator(
|
|
287
|
+
abbreviate_rte=not no_abbreviate_rte,
|
|
288
|
+
use_module_names=use_module_names,
|
|
289
|
+
)
|
|
290
|
+
generator.generate(result, str(mermaid_output))
|
|
291
|
+
|
|
292
|
+
progress.update(task, completed=True)
|
|
293
|
+
|
|
294
|
+
console.print(
|
|
295
|
+
f"[green]Generated[/green] Mermaid diagram: [cyan]{mermaid_output}[/cyan]"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if format == "xmi":
|
|
299
|
+
console.print("[yellow]Warning:[/yellow] XMI format not yet implemented")
|
|
300
|
+
|
|
301
|
+
if format == "both":
|
|
302
|
+
console.print(
|
|
303
|
+
"[yellow]Warning:[/yellow] XMI format not yet implemented (only Mermaid generated)"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Print warnings for circular dependencies
|
|
307
|
+
if result.circular_dependencies:
|
|
308
|
+
console.print(
|
|
309
|
+
"\n[bold yellow]Warning:[/bold yellow] Circular dependencies detected!"
|
|
310
|
+
)
|
|
311
|
+
for idx, circ_dep in enumerate(result.circular_dependencies, 1):
|
|
312
|
+
cycle_str = " → ".join(circ_dep.cycle)
|
|
313
|
+
console.print(
|
|
314
|
+
f" {idx}. [yellow]{cycle_str}[/yellow] (depth {circ_dep.depth})"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
console.print("\n[bold green]Analysis complete![/bold green]")
|
|
318
|
+
|
|
319
|
+
except KeyboardInterrupt:
|
|
320
|
+
console.print("\n[yellow]Interrupted by user[/yellow]")
|
|
321
|
+
sys.exit(130)
|
|
322
|
+
except Exception as e:
|
|
323
|
+
console.print(f"\n[bold red]Error:[/bold red] {e}")
|
|
324
|
+
if verbose:
|
|
325
|
+
console.print_exception()
|
|
326
|
+
sys.exit(1)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
if __name__ == "__main__":
|
|
330
|
+
cli()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for AUTOSAR Call Tree Analyzer.
|
|
3
|
+
|
|
4
|
+
This module provides functionality for managing YAML-based configurations,
|
|
5
|
+
including mapping C source files to SW modules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .module_config import ModuleConfig
|
|
9
|
+
|
|
10
|
+
__all__ = ["ModuleConfig"]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module configuration management for SW module mappings.
|
|
3
|
+
|
|
4
|
+
This module provides functionality for loading and managing YAML-based
|
|
5
|
+
configurations that map C source files to SW (Software) modules.
|
|
6
|
+
|
|
7
|
+
Requirements:
|
|
8
|
+
- SWR_CONFIG_00001: YAML Configuration File Support
|
|
9
|
+
- SWR_CONFIG_00002: Module Configuration Validation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import fnmatch
|
|
13
|
+
import re
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, List, Optional, Tuple
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ModuleConfig:
|
|
19
|
+
"""
|
|
20
|
+
Manages SW module configuration from YAML file.
|
|
21
|
+
|
|
22
|
+
This class handles loading, validating, and looking up SW module mappings
|
|
23
|
+
for C source files. It supports both specific file mappings and glob pattern
|
|
24
|
+
mappings.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
specific_mappings: Dictionary mapping exact filenames to module names
|
|
28
|
+
pattern_mappings: List of compiled regex patterns and module names
|
|
29
|
+
default_module: Default module name for unmapped files (optional)
|
|
30
|
+
_lookup_cache: Cache for filename to module lookups
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, config_path: Optional[Path] = None) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Initialize the module configuration.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
config_path: Path to YAML configuration file (optional)
|
|
39
|
+
"""
|
|
40
|
+
self.specific_mappings: Dict[str, str] = {}
|
|
41
|
+
self.pattern_mappings: List[Tuple[re.Pattern, str]] = []
|
|
42
|
+
self.default_module: Optional[str] = None
|
|
43
|
+
self._lookup_cache: Dict[str, Optional[str]] = {}
|
|
44
|
+
|
|
45
|
+
if config_path:
|
|
46
|
+
self.load_config(config_path)
|
|
47
|
+
|
|
48
|
+
def load_config(self, config_path: Path) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Load module configuration from YAML file.
|
|
51
|
+
|
|
52
|
+
Implements: SWR_CONFIG_00001 (YAML Configuration File Support)
|
|
53
|
+
Implements: SWR_CONFIG_00002 (Module Configuration Validation)
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
config_path: Path to YAML configuration file
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
FileNotFoundError: If config file doesn't exist
|
|
60
|
+
ValueError: If config file format is invalid
|
|
61
|
+
"""
|
|
62
|
+
import yaml # type: ignore
|
|
63
|
+
|
|
64
|
+
if not config_path.exists():
|
|
65
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
66
|
+
|
|
67
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
68
|
+
data = yaml.safe_load(f)
|
|
69
|
+
|
|
70
|
+
if not isinstance(data, dict):
|
|
71
|
+
raise ValueError(
|
|
72
|
+
"Invalid configuration format: expected dictionary at root level"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Load specific file mappings (exact filename match)
|
|
76
|
+
file_mappings = data.get("file_mappings", {})
|
|
77
|
+
if not isinstance(file_mappings, dict):
|
|
78
|
+
raise ValueError("'file_mappings' must be a dictionary")
|
|
79
|
+
|
|
80
|
+
for filename, module in file_mappings.items():
|
|
81
|
+
if not isinstance(filename, str) or not isinstance(module, str):
|
|
82
|
+
raise ValueError("File mappings must be strings")
|
|
83
|
+
if not module.strip():
|
|
84
|
+
raise ValueError(f"Module name cannot be empty for file: {filename}")
|
|
85
|
+
self.specific_mappings[filename] = module
|
|
86
|
+
|
|
87
|
+
# Load pattern mappings (glob patterns)
|
|
88
|
+
pattern_mappings = data.get("pattern_mappings", {})
|
|
89
|
+
if not isinstance(pattern_mappings, dict):
|
|
90
|
+
raise ValueError("'pattern_mappings' must be a dictionary")
|
|
91
|
+
|
|
92
|
+
for pattern, module in pattern_mappings.items():
|
|
93
|
+
if not isinstance(pattern, str) or not isinstance(module, str):
|
|
94
|
+
raise ValueError("Pattern mappings must be strings")
|
|
95
|
+
if not module.strip():
|
|
96
|
+
raise ValueError(f"Module name cannot be empty for pattern: {pattern}")
|
|
97
|
+
|
|
98
|
+
# Compile glob pattern to regex for faster matching
|
|
99
|
+
regex = fnmatch.translate(pattern)
|
|
100
|
+
compiled = re.compile(regex)
|
|
101
|
+
self.pattern_mappings.append((compiled, module))
|
|
102
|
+
|
|
103
|
+
# Load default module (optional)
|
|
104
|
+
default_module = data.get("default_module")
|
|
105
|
+
if default_module is not None:
|
|
106
|
+
if not isinstance(default_module, str) or not default_module.strip():
|
|
107
|
+
raise ValueError("'default_module' must be a non-empty string")
|
|
108
|
+
self.default_module = default_module
|
|
109
|
+
|
|
110
|
+
def get_module_for_file(self, file_path: Path) -> Optional[str]:
|
|
111
|
+
"""
|
|
112
|
+
Get SW module name for a given file.
|
|
113
|
+
|
|
114
|
+
This method first checks specific file mappings, then pattern mappings,
|
|
115
|
+
and finally returns the default module if configured.
|
|
116
|
+
|
|
117
|
+
Lookup results are cached for performance.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
file_path: Path to the source file
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Module name if found, None otherwise
|
|
124
|
+
"""
|
|
125
|
+
filename = file_path.name
|
|
126
|
+
|
|
127
|
+
# Check cache first
|
|
128
|
+
if filename in self._lookup_cache:
|
|
129
|
+
return self._lookup_cache[filename]
|
|
130
|
+
|
|
131
|
+
# Check specific file mappings (exact match)
|
|
132
|
+
if filename in self.specific_mappings:
|
|
133
|
+
module = self.specific_mappings[filename]
|
|
134
|
+
self._lookup_cache[filename] = module
|
|
135
|
+
return module
|
|
136
|
+
|
|
137
|
+
# Check pattern mappings (glob patterns)
|
|
138
|
+
for pattern, module in self.pattern_mappings:
|
|
139
|
+
if pattern.match(filename):
|
|
140
|
+
self._lookup_cache[filename] = module
|
|
141
|
+
return module
|
|
142
|
+
|
|
143
|
+
# Use default module if configured
|
|
144
|
+
if self.default_module is not None:
|
|
145
|
+
self._lookup_cache[filename] = self.default_module
|
|
146
|
+
return self.default_module
|
|
147
|
+
|
|
148
|
+
# No match found
|
|
149
|
+
self._lookup_cache[filename] = None
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def validate_config(self) -> List[str]:
|
|
153
|
+
"""
|
|
154
|
+
Validate the current configuration.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
List of validation error messages (empty if valid)
|
|
158
|
+
"""
|
|
159
|
+
errors = []
|
|
160
|
+
|
|
161
|
+
if not self.specific_mappings and not self.pattern_mappings:
|
|
162
|
+
errors.append(
|
|
163
|
+
"Configuration must contain either 'file_mappings' or 'pattern_mappings'"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return errors
|
|
167
|
+
|
|
168
|
+
def get_statistics(self) -> Dict[str, int]:
|
|
169
|
+
"""
|
|
170
|
+
Get statistics about the configuration.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dictionary with configuration statistics
|
|
174
|
+
"""
|
|
175
|
+
return {
|
|
176
|
+
"specific_file_mappings": len(self.specific_mappings),
|
|
177
|
+
"pattern_mappings": len(self.pattern_mappings),
|
|
178
|
+
"has_default_module": 1 if self.default_module else 0,
|
|
179
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Database package initialization."""
|
|
2
|
+
|
|
3
|
+
from .models import (
|
|
4
|
+
AnalysisResult,
|
|
5
|
+
AnalysisStatistics,
|
|
6
|
+
CallTreeNode,
|
|
7
|
+
CircularDependency,
|
|
8
|
+
FunctionDict,
|
|
9
|
+
FunctionInfo,
|
|
10
|
+
FunctionType,
|
|
11
|
+
Parameter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"FunctionType",
|
|
16
|
+
"Parameter",
|
|
17
|
+
"FunctionInfo",
|
|
18
|
+
"CallTreeNode",
|
|
19
|
+
"CircularDependency",
|
|
20
|
+
"AnalysisStatistics",
|
|
21
|
+
"AnalysisResult",
|
|
22
|
+
"FunctionDict",
|
|
23
|
+
]
|