|
24 | 24 |
|
25 | 25 | import ast |
26 | 26 | import os |
| 27 | +import glob |
| 28 | +import logging |
| 29 | +import re |
27 | 30 | from collections import defaultdict |
28 | | -from typing import List, Dict, Any |
| 31 | +from typing import List, Dict, Any, Iterator |
29 | 32 |
|
| 33 | +from . import name_utils |
30 | 34 | from . import utils |
31 | 35 |
|
32 | 36 | # ============================================================================= |
@@ -352,3 +356,139 @@ def process_structure( |
352 | 356 | return sorted(all_class_keys) |
353 | 357 | else: |
354 | 358 | return dict(results) |
| 359 | + |
| 360 | + |
| 361 | +# ============================================================================= |
| 362 | +# Section 2: Source file data gathering |
| 363 | +# ============================================================================= |
| 364 | + |
| 365 | + |
| 366 | +def _should_include_class(class_name: str, class_filters: Dict[str, Any]) -> bool: |
| 367 | + """Checks if a class should be included based on filter criteria.""" |
| 368 | + if class_filters.get("include_suffixes"): |
| 369 | + if not class_name.endswith(tuple(class_filters["include_suffixes"])): |
| 370 | + return False |
| 371 | + if class_filters.get("exclude_suffixes"): |
| 372 | + if class_name.endswith(tuple(class_filters["exclude_suffixes"])): |
| 373 | + return False |
| 374 | + return True |
| 375 | + |
| 376 | + |
| 377 | +def _should_include_method(method_name: str, method_filters: Dict[str, Any]) -> bool: |
| 378 | + """Checks if a method should be included based on filter criteria.""" |
| 379 | + if method_filters.get("include_prefixes"): |
| 380 | + if not any( |
| 381 | + method_name.startswith(p) for p in method_filters["include_prefixes"] |
| 382 | + ): |
| 383 | + return False |
| 384 | + if method_filters.get("exclude_prefixes"): |
| 385 | + if any(method_name.startswith(p) for p in method_filters["exclude_prefixes"]): |
| 386 | + return False |
| 387 | + return True |
| 388 | + |
| 389 | + |
| 390 | +def _build_request_arg_schema( |
| 391 | + source_files: List[str], project_root: str |
| 392 | +) -> Dict[str, List[str]]: |
| 393 | + """Parses type files to build a schema of request classes and their _id arguments.""" |
| 394 | + request_arg_schema: Dict[str, List[str]] = {} |
| 395 | + for file_path in source_files: |
| 396 | + if "/types/" not in file_path: |
| 397 | + continue |
| 398 | + |
| 399 | + # Correctly determine the module name from the file path |
| 400 | + relative_path = os.path.relpath(file_path, project_root) |
| 401 | + module_name = os.path.splitext(relative_path)[0].replace(os.path.sep, ".") |
| 402 | + |
| 403 | + try: |
| 404 | + structure, _, _ = parse_file(file_path) |
| 405 | + if not structure: |
| 406 | + continue |
| 407 | + |
| 408 | + for class_info in structure: |
| 409 | + class_name = class_info.get("class_name", "Unknown") |
| 410 | + if class_name.endswith("Request"): |
| 411 | + full_class_name = f"{module_name}.{class_name}" |
| 412 | + id_args = [ |
| 413 | + attr["name"] |
| 414 | + for attr in class_info.get("attributes", []) |
| 415 | + if attr.get("name", "").endswith("_id") |
| 416 | + ] |
| 417 | + if id_args: |
| 418 | + request_arg_schema[full_class_name] = id_args |
| 419 | + except Exception as e: |
| 420 | + logging.warning(f"Failed to parse {file_path}: {e}") |
| 421 | + return request_arg_schema |
| 422 | + |
| 423 | + |
| 424 | +def _process_service_clients( |
| 425 | + source_files: List[str], class_filters: Dict, method_filters: Dict |
| 426 | +) -> tuple[defaultdict, set, set]: |
| 427 | + """Parses service client files to extract class and method information.""" |
| 428 | + parsed_data = defaultdict(dict) |
| 429 | + all_imports: set[str] = set() |
| 430 | + all_types: set[str] = set() |
| 431 | + |
| 432 | + for file_path in source_files: |
| 433 | + if "/services/" not in file_path: |
| 434 | + continue |
| 435 | + |
| 436 | + structure, imports, types = parse_file(file_path) |
| 437 | + all_imports.update(imports) |
| 438 | + all_types.update(types) |
| 439 | + |
| 440 | + for class_info in structure: |
| 441 | + class_name = class_info["class_name"] |
| 442 | + if not _should_include_class(class_name, class_filters): |
| 443 | + continue |
| 444 | + |
| 445 | + parsed_data[class_name] # Ensure class is in dict |
| 446 | + |
| 447 | + for method in class_info["methods"]: |
| 448 | + method_name = method["method_name"] |
| 449 | + if not _should_include_method(method_name, method_filters): |
| 450 | + continue |
| 451 | + parsed_data[class_name][method_name] = method |
| 452 | + return parsed_data, all_imports, all_types |
| 453 | + |
| 454 | + |
| 455 | +def analyze_source_files( |
| 456 | + config: Dict[str, Any], |
| 457 | +) -> tuple[Dict[str, Any], set[str], set[str], Dict[str, List[str]]]: |
| 458 | + """ |
| 459 | + Analyzes source files per the configuration to extract class and method info, |
| 460 | + as well as information on imports and typehints. |
| 461 | +
|
| 462 | + Args: |
| 463 | + config: The generator's configuration dictionary. |
| 464 | +
|
| 465 | + Returns: |
| 466 | + A tuple containing: |
| 467 | + - A dictionary containing the data needed for template rendering. |
| 468 | + - A set of all import statements required by the parsed methods. |
| 469 | + - A set of all type annotations found in the parsed methods. |
| 470 | + - A dictionary mapping request class names to their `_id` arguments. |
| 471 | + """ |
| 472 | + project_root = config["project_root"] |
| 473 | + source_patterns_dict = config.get("source_files", {}) |
| 474 | + filter_rules = config.get("filter", {}) |
| 475 | + class_filters = filter_rules.get("classes", {}) |
| 476 | + method_filters = filter_rules.get("methods", {}) |
| 477 | + |
| 478 | + source_files = [] |
| 479 | + for group in source_patterns_dict.values(): |
| 480 | + for pattern in group: |
| 481 | + # Make the pattern absolute |
| 482 | + absolute_pattern = os.path.join(project_root, pattern) |
| 483 | + source_files.extend(glob.glob(absolute_pattern, recursive=True)) |
| 484 | + |
| 485 | + # PASS 1: Build the request argument schema from the types files. |
| 486 | + request_arg_schema = _build_request_arg_schema(source_files, project_root) |
| 487 | + |
| 488 | + # PASS 2: Process the service client files. |
| 489 | + parsed_data, all_imports, all_types = _process_service_clients( |
| 490 | + source_files, class_filters, method_filters |
| 491 | + ) |
| 492 | + |
| 493 | + return parsed_data, all_imports, all_types, request_arg_schema |
| 494 | + |
0 commit comments