Coverage for src/c2puml/core/transformer.py: 62%

792 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-14 18:23 +0000

1#!/usr/bin/env python3 

2"""""" 

3 

4import json 

5import logging 

6import re 

7from pathlib import Path 

8from typing import Any, Callable, Dict, List, Optional, Pattern, Set, Tuple, Union as TypingUnion 

9from collections import deque 

10 

11from ..models import ( 

12 Alias, 

13 Enum, 

14 EnumValue, 

15 Field, 

16 FileModel, 

17 Function, 

18 IncludeRelation, 

19 ProjectModel, 

20 Struct, 

21 Union, 

22) 

23 

24 

25class Transformer: 

26 """""" 

27 

28 def __init__(self) -> None: 

29 self.logger = logging.getLogger(__name__) 

30 

31 def transform( 

32 self, model_file: str, config_file: str, output_file: Optional[str] = None 

33 ) -> str: 

34 """""" 

35 self.logger.info("Step 2: Transforming model: %s", model_file) 

36 

37 model = self._load_model(model_file) 

38 config = self._load_config(config_file) 

39 

40 transformed_model = self._apply_transformations(model, config) 

41 

42 output_path = output_file or model_file 

43 self._save_model(transformed_model, output_path) 

44 

45 self.logger.info("Step 2 complete! Transformed model saved to: %s", output_path) 

46 return output_path 

47 

48 def _load_model(self, model_file: str) -> ProjectModel: 

49 """""" 

50 model_path = Path(model_file) 

51 if not model_path.exists(): 

52 raise FileNotFoundError(f"Model file not found: {model_file}") 

53 

54 try: 

55 model = ProjectModel.load(model_file) 

56 self.logger.debug("Loaded model with %d files", len(model.files)) 

57 return model 

58 except Exception as e: 

59 raise ValueError(f"Failed to load model from {model_file}: {e}") from e 

60 

61 def _load_config(self, config_file: str) -> Dict[str, Any]: 

62 """""" 

63 config_path = Path(config_file) 

64 if not config_path.exists(): 

65 raise FileNotFoundError(f"Configuration file not found: {config_file}") 

66 

67 try: 

68 with open(config_file, "r", encoding="utf-8") as f: 

69 config = json.load(f) 

70 

71 self.logger.debug("Loaded configuration from: %s", config_file) 

72 return config 

73 

74 except Exception as e: 

75 raise ValueError( 

76 f"Failed to load configuration from {config_file}: {e}" 

77 ) from e 

78 

79 def _apply_transformations( 

80 self, model: ProjectModel, config: Dict[str, Any] 

81 ) -> ProjectModel: 

82 """""" 

83 self.logger.info("Applying transformations to model") 

84 

85 if "file_filters" in config: 

86 model = self._apply_file_filters(model, config["file_filters"]) 

87 

88 config = self._ensure_backward_compatibility(config) 

89 

90 model = self._apply_transformation_containers(model, config) 

91 

92 if self._should_process_include_relations(config): 

93 model = self._process_include_relations_simplified(model, config) 

94 

95 self.logger.info( 

96 "Transformations complete. Model now has %d files", len(model.files) 

97 ) 

98 return model 

99 

100 def _apply_transformation_containers( 

101 self, model: ProjectModel, config: Dict[str, Any] 

102 ) -> ProjectModel: 

103 """""" 

104 transformation_containers = self._discover_transformation_containers(config) 

105 

106 if not transformation_containers: 

107 return model 

108 

109 for container_name, transformation_config in transformation_containers: 

110 self.logger.info("Applying transformation container: %s", container_name) 

111 model = self._apply_single_transformation_container( 

112 model, transformation_config, container_name 

113 ) 

114 self._log_model_state_after_container(model, container_name) 

115 

116 return model 

117 

118 def _log_model_state_after_container( 

119 self, model: ProjectModel, container_name: str 

120 ) -> None: 

121 """""" 

122 total_elements = sum( 

123 len(file_model.structs) + len(file_model.enums) + len(file_model.unions) + 

124 len(file_model.functions) + len(file_model.globals) + len(file_model.macros) + 

125 len(file_model.aliases) 

126 for file_model in model.files.values() 

127 ) 

128 self.logger.info( 

129 "After %s: model contains %d files with %d total elements", 

130 container_name, len(model.files), total_elements 

131 ) 

132 

133 def _should_process_include_relations(self, config: Dict[str, Any]) -> bool: 

134 """""" 

135 if config.get("include_depth", 1) > 1: 

136 return True 

137 

138 if "file_specific" in config: 

139 for file_config in config["file_specific"].values(): 

140 if file_config.get("include_depth", 1) > 1: 

141 return True 

142 

143 return False 

144 

145 def _discover_transformation_containers(self, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: 

146 """""" 

147 transformation_containers = [ 

148 (key, value) 

149 for key, value in config.items() 

150 if key.startswith("transformations") and isinstance(value, dict) 

151 ] 

152 

153 transformation_containers.sort(key=lambda x: x[0]) 

154 

155 self.logger.info( 

156 "Discovered %d transformation containers: %s", 

157 len(transformation_containers), 

158 [name for name, _ in transformation_containers] 

159 ) 

160 

161 return transformation_containers 

162 

163 def _ensure_backward_compatibility(self, config: Dict[str, Any]) -> Dict[str, Any]: 

164 """""" 

165 config = config.copy() 

166 

167 if self._is_legacy_transformation_format(config): 

168 self.logger.info("Converting legacy 'transformations' format to container format") 

169 old_transformations = config.pop("transformations") 

170 config["transformations_00_default"] = old_transformations 

171 self.logger.debug("Converted to container: transformations_00_default") 

172 

173 return config 

174 

175 def _is_legacy_transformation_format(self, config: Dict[str, Any]) -> bool: 

176 """""" 

177 return ( 

178 "transformations" in config and 

179 not any(key.startswith("transformations_") for key in config.keys()) 

180 ) 

181 

182 def _apply_single_transformation_container( 

183 self, 

184 model: ProjectModel, 

185 transformation_config: Dict[str, Any], 

186 container_name: str 

187 ) -> ProjectModel: 

188 """""" 

189 self.logger.debug("Processing transformation container: %s", container_name) 

190 

191 target_files = self._get_target_files(model, transformation_config) 

192 

193 model = self._apply_remove_operations(model, transformation_config, target_files, container_name) 

194 model = self._apply_rename_operations(model, transformation_config, target_files, container_name) 

195 model = self._apply_add_operations(model, transformation_config, target_files, container_name) 

196 

197 return model 

198 

199 def _get_target_files( 

200 self, model: ProjectModel, transformation_config: Dict[str, Any] 

201 ) -> Set[str]: 

202 """""" 

203 selected_files = transformation_config.get("file_selection", []) 

204 

205 if not isinstance(selected_files, list): 

206 selected_files = [] 

207 self.logger.warning("Invalid file_selection format, must be a list, defaulting to empty list") 

208 

209 if not selected_files: 

210 target_files = set(model.files.keys()) 

211 self.logger.debug("No file selection specified, applying to all %d files", len(target_files)) 

212 else: 

213 target_files = self._match_files_by_patterns(model, selected_files) 

214 self.logger.debug( 

215 "File selection patterns %s matched %d files: %s", 

216 selected_files, len(target_files), list(target_files) 

217 ) 

218 

219 return target_files 

220 

221 def _match_files_by_patterns( 

222 self, model: ProjectModel, patterns: List[str] 

223 ) -> Set[str]: 

224 """""" 

225 target_files = set() 

226 for pattern in patterns: 

227 for file_path in model.files.keys(): 

228 if self._matches_pattern(file_path, pattern): 

229 target_files.add(file_path) 

230 return target_files 

231 

232 def _apply_remove_operations( 

233 self, 

234 model: ProjectModel, 

235 transformation_config: Dict[str, Any], 

236 target_files: Set[str], 

237 container_name: str 

238 ) -> ProjectModel: 

239 """""" 

240 if "remove" not in transformation_config: 

241 return model 

242 

243 self.logger.debug("Applying remove operations for container: %s", container_name) 

244 

245 removed_typedef_names = self._collect_typedef_names_for_removal( 

246 model, transformation_config["remove"], target_files 

247 ) 

248 

249 model = self._apply_removals(model, transformation_config["remove"], target_files) 

250 

251 if removed_typedef_names: 

252 self.logger.debug("Calling type reference cleanup for container: %s", container_name) 

253 self._cleanup_type_references_by_names(model, removed_typedef_names) 

254 

255 return model 

256 

257 def _apply_rename_operations( 

258 self, 

259 model: ProjectModel, 

260 transformation_config: Dict[str, Any], 

261 target_files: Set[str], 

262 container_name: str 

263 ) -> ProjectModel: 

264 """""" 

265 if "rename" not in transformation_config: 

266 return model 

267 

268 self.logger.debug("Applying rename operations for container: %s", container_name) 

269 return self._apply_renaming(model, transformation_config["rename"], target_files) 

270 

271 def _apply_add_operations( 

272 self, 

273 model: ProjectModel, 

274 transformation_config: Dict[str, Any], 

275 target_files: Set[str], 

276 container_name: str 

277 ) -> ProjectModel: 

278 """""" 

279 if "add" not in transformation_config: 

280 return model 

281 

282 self.logger.debug("Applying add operations for container: %s", container_name) 

283 return self._apply_additions(model, transformation_config["add"], target_files) 

284 

285 def _collect_typedef_names_for_removal( 

286 self, 

287 model: ProjectModel, 

288 remove_config: Dict[str, Any], 

289 target_files: Set[str] 

290 ) -> Set[str]: 

291 """""" 

292 removed_typedef_names = set() 

293 

294 if "typedef" not in remove_config: 

295 return removed_typedef_names 

296 

297 typedef_patterns = remove_config["typedef"] 

298 compiled_patterns = self._compile_patterns(typedef_patterns) 

299 

300 if not compiled_patterns: 

301 return removed_typedef_names 

302 

303 for file_path in target_files: 

304 if file_path in model.files: 

305 file_model = model.files[file_path] 

306 for alias_name in file_model.aliases.keys(): 

307 if self._matches_any_pattern(alias_name, compiled_patterns): 

308 removed_typedef_names.add(alias_name) 

309 

310 self.logger.debug("Pre-identified typedefs for removal: %s", list(removed_typedef_names)) 

311 return removed_typedef_names 

312 

313 def _process_include_relations_simplified( 

314 self, model: ProjectModel, config: Dict[str, Any] 

315 ) -> ProjectModel: 

316 """""" 

317 global_include_depth = config.get("include_depth", 1) 

318 file_specific_config = config.get("file_specific", {}) 

319 include_filter_local_only = config.get("include_filter_local_only", False) 

320 always_show_includes = config.get("always_show_includes", False) 

321 

322 self.logger.info( 

323 "Processing includes with simplified depth-based approach (global_depth=%d)", 

324 global_include_depth 

325 ) 

326 

327 for file_model in model.files.values(): 

328 file_model.include_relations = [] 

329 

330 file_map = {} 

331 for file_model in model.files.values(): 

332 filename = Path(file_model.name).name 

333 file_map[filename] = file_model 

334 

335 c_files = sorted([ 

336 fm for fm in model.files.values() if fm.name.endswith(".c") 

337 ], key=lambda fm: fm.name) 

338 

339 for root_file in c_files: 

340 self._process_root_c_file_includes( 

341 root_file, file_map, global_include_depth, file_specific_config, include_filter_local_only, always_show_includes 

342 ) 

343 

344 return model 

345 

346 def _process_root_c_file_includes( 

347 self, 

348 root_file: FileModel, 

349 file_map: Dict[str, FileModel], 

350 global_include_depth: int, 

351 file_specific_config: Dict[str, Any], 

352 include_filter_local_only: bool, 

353 always_show_includes: bool 

354 ) -> None: 

355 """""" 

356 root_filename = Path(root_file.name).name 

357 

358 include_depth = global_include_depth 

359 include_filters = [] 

360 

361 if root_filename in file_specific_config: 

362 file_config = file_specific_config[root_filename] 

363 include_depth = file_config.get("include_depth", global_include_depth) 

364 include_filters = file_config.get("include_filter", []) 

365 

366 if include_filter_local_only: 

367 local_header_pattern = f"^{Path(root_filename).stem}\\.h$" 

368 if local_header_pattern not in include_filters: 

369 include_filters.append(local_header_pattern) 

370 

371 if include_depth <= 1: 

372 self.logger.debug( 

373 "Skipping include processing for %s (depth=%d)", 

374 root_filename, include_depth 

375 ) 

376 return 

377 

378 compiled_filters = [] 

379 if include_filters: 

380 try: 

381 compiled_filters = [re.compile(pattern) for pattern in include_filters] 

382 self.logger.debug( 

383 "Compiled %d filter patterns for %s", 

384 len(compiled_filters), root_filename 

385 ) 

386 except re.error as e: 

387 self.logger.warning( 

388 "Invalid regex pattern for %s: %s", root_filename, e 

389 ) 

390 

391 self.logger.debug( 

392 "Processing includes for root C file %s (depth=%d, filters=%d)", 

393 root_filename, include_depth, len(compiled_filters) 

394 ) 

395 

396 processed_files = set() 

397 try: 

398 root_file.placeholder_headers.clear() 

399 except Exception: 

400 root_file.placeholder_headers = set() 

401 

402 current_level = [root_file] 

403 

404 for depth in range(1, include_depth + 1): 

405 next_level = [] 

406 

407 self.logger.debug( 

408 "Processing depth %d for %s (%d files at current level)", 

409 depth, root_filename, len(current_level) 

410 ) 

411 

412 for current_file in sorted(current_level, key=lambda fm: Path(fm.name).name): 

413 current_filename = Path(current_file.name).name 

414 

415 if current_filename in processed_files: 

416 continue 

417 processed_files.add(current_filename) 

418 

419 for include_name in sorted(current_file.includes): 

420 filtered_out_by_patterns = False 

421 if compiled_filters: 

422 if not any(pattern.search(include_name) for pattern in compiled_filters): 

423 if always_show_includes: 

424 filtered_out_by_patterns = True 

425 self.logger.debug( 

426 "Include %s filtered by patterns at depth %d for %s, but will be shown as placeholder", 

427 include_name, depth, root_filename 

428 ) 

429 # Intentionally do not continue here; still add relation and mark placeholder 

430 else: 

431 self.logger.debug( 

432 "Filtered out include %s at depth %d for %s", 

433 include_name, depth, root_filename 

434 ) 

435 continue 

436 

437 if include_name not in file_map: 

438 self.logger.debug( 

439 "Include %s not found in project files (depth %d, root %s)", 

440 include_name, depth, root_filename 

441 ) 

442 continue 

443 

444 if include_name == current_filename: 

445 self.logger.debug( 

446 "Skipping self-reference %s at depth %d for %s", 

447 include_name, depth, root_filename 

448 ) 

449 continue 

450 

451 existing_relation = any( 

452 rel.source_file == current_filename and rel.included_file == include_name 

453 for rel in root_file.include_relations 

454 ) 

455 

456 if existing_relation: 

457 self.logger.debug( 

458 "Skipping duplicate relation %s -> %s for %s", 

459 current_filename, include_name, root_filename 

460 ) 

461 continue 

462 

463 relation = IncludeRelation( 

464 source_file=current_filename, 

465 included_file=include_name, 

466 depth=depth 

467 ) 

468 root_file.include_relations.append(relation) 

469 

470 self.logger.debug( 

471 "Added include relation: %s -> %s (depth %d) for root %s", 

472 current_filename, include_name, depth, root_filename 

473 ) 

474 

475 if filtered_out_by_patterns: 

476 try: 

477 root_file.placeholder_headers.add(include_name) 

478 except Exception: 

479 root_file.placeholder_headers = {include_name} 

480 continue 

481 

482 included_file = file_map[include_name] 

483 if included_file not in next_level and include_name not in processed_files: 

484 next_level.append(included_file) 

485 

486 current_level = sorted(next_level, key=lambda fm: Path(fm.name).name) 

487 

488 if not current_level: 

489 self.logger.debug( 

490 "No more files to process at depth %d for %s", 

491 depth + 1, root_filename 

492 ) 

493 break 

494 

495 self.logger.debug( 

496 "Completed include processing for %s: %d relations generated", 

497 root_filename, len(root_file.include_relations) 

498 ) 

499 

500 def _apply_file_filters( 

501 self, model: ProjectModel, filters: Dict[str, Any] 

502 ) -> ProjectModel: 

503 """""" 

504 include_patterns = self._compile_patterns(filters.get("include", [])) 

505 exclude_patterns = self._compile_patterns(filters.get("exclude", [])) 

506 

507 if not include_patterns and not exclude_patterns: 

508 return model 

509 

510 filtered_files = {} 

511 for file_path, file_model in model.files.items(): 

512 if self._should_include_file(file_path, include_patterns, exclude_patterns): 

513 filtered_files[file_path] = file_model 

514 

515 model.files = filtered_files 

516 self.logger.debug( 

517 "User file filtering: %d files after filtering", len(model.files) 

518 ) 

519 return model 

520 

521 def _apply_include_filters( 

522 self, model: ProjectModel, include_filters: Dict[str, List[str]] 

523 ) -> ProjectModel: 

524 """""" 

525 self.logger.info( 

526 "Applying include filters for %d root files", len(include_filters) 

527 ) 

528 

529 compiled_filters = {} 

530 for root_file, patterns in include_filters.items(): 

531 try: 

532 compiled_filters[root_file] = [ 

533 re.compile(pattern) for pattern in patterns 

534 ] 

535 self.logger.debug( 

536 "Compiled %d patterns for root file: %s", len(patterns), root_file 

537 ) 

538 except re.error as e: 

539 self.logger.warning( 

540 "Invalid regex pattern for root file %s: %s", root_file, e 

541 ) 

542 continue 

543 

544 if not compiled_filters: 

545 self.logger.warning( 

546 "No valid include filters found, skipping include filtering" 

547 ) 

548 return model 

549 

550 header_to_root = self._create_header_to_root_mapping(model) 

551 

552 for file_path, file_model in model.files.items(): 

553 root_file = self._find_root_file_with_mapping( 

554 file_path, file_model, header_to_root 

555 ) 

556 

557 if root_file in compiled_filters: 

558 self._filter_include_relations( 

559 file_model, compiled_filters[root_file], root_file 

560 ) 

561 

562 return model 

563 

564 def _create_header_to_root_mapping(self, model: ProjectModel) -> Dict[str, str]: 

565 """""" 

566 header_to_root = {} 

567 c_files = [] 

568 for file_path, file_model in model.files.items(): 

569 if file_model.name.endswith(".c"): 

570 header_to_root[file_model.name] = file_model.name 

571 c_files.append(file_model.name) 

572 for file_path, file_model in model.files.items(): 

573 if not file_model.name.endswith(".c"): 

574 header_base_name = Path(file_model.name).stem 

575 matching_c_file = header_base_name + ".c" 

576 

577 if matching_c_file in [Path(c_file).name for c_file in c_files]: 

578 header_to_root[file_model.name] = matching_c_file 

579 else: 

580 including_c_files = [] 

581 for c_file_path, c_file_model in model.files.items(): 

582 if (c_file_model.name.endswith(".c") and 

583 file_model.name in c_file_model.includes): 

584 including_c_files.append(c_file_model.name) 

585 

586 if including_c_files: 

587 header_to_root[file_model.name] = including_c_files[0] 

588 else: 

589 if c_files: 

590 header_to_root[file_model.name] = c_files[0] 

591 return header_to_root 

592 

593 def _find_root_file_with_mapping( 

594 self, file_path: str, file_model: FileModel, header_to_root: Dict[str, str] 

595 ) -> str: 

596 """""" 

597 if file_model.name.endswith(".c"): 

598 return file_model.name 

599 return header_to_root.get(file_model.name, file_model.name) 

600 

601 def _find_root_file(self, file_path: str, file_model: FileModel) -> str: 

602 """""" 

603 filename = Path(file_path).name 

604 if filename.endswith(".c"): 

605 return filename 

606 base_name = Path(file_path).stem 

607 if base_name and not filename.startswith("."): 

608 return base_name + ".c" 

609 return filename 

610 

611 def _filter_include_relations( 

612 self, file_model: FileModel, patterns: List[re.Pattern], root_file: str 

613 ) -> None: 

614 """""" 

615 self.logger.debug( 

616 "Filtering include_relations for file %s (root: %s)", file_model.name, root_file 

617 ) 

618 

619 original_relations_count = len(file_model.include_relations) 

620 filtered_relations: List[IncludeRelation] = [] 

621 

622 for relation in file_model.include_relations: 

623 if self._matches_any_pattern(relation.included_file, patterns): 

624 filtered_relations.append(relation) 

625 else: 

626 self.logger.debug( 

627 "Filtered out include relation: %s -> %s (root: %s)", 

628 relation.source_file, 

629 relation.included_file, 

630 root_file, 

631 ) 

632 try: 

633 file_model.placeholder_headers.add(relation.included_file) 

634 except Exception: 

635 file_model.placeholder_headers = {relation.included_file} 

636 

637 file_model.include_relations = filtered_relations 

638 

639 self.logger.debug( 

640 "Include filtering for %s: relations %d->%d (includes preserved)", 

641 file_model.name, 

642 original_relations_count, 

643 len(file_model.include_relations), 

644 ) 

645 

646 def _matches_any_pattern(self, text: str, patterns: List[Pattern[str]]) -> bool: 

647 """""" 

648 return any(pattern.search(text) for pattern in patterns) 

649 

650 def _matches_pattern(self, text: str, pattern: str) -> bool: 

651 """""" 

652 try: 

653 return bool(re.search(pattern, text)) 

654 except re.error as e: 

655 self.logger.warning("Invalid regex pattern '%s': %s", pattern, e) 

656 return False 

657 

658 # Removed unused _apply_model_transformations (legacy API) 

659 

660 def _apply_renaming( 

661 self, model: ProjectModel, rename_config: Dict[str, Any], target_files: Set[str] 

662 ) -> ProjectModel: 

663 """""" 

664 self.logger.debug( 

665 "Applying renaming transformations to %d files", len(target_files) 

666 ) 

667 

668 for file_path in target_files: 

669 if file_path in model.files: 

670 file_model = model.files[file_path] 

671 self.logger.debug("Applying renaming to file: %s", file_path) 

672 self._apply_file_level_renaming(file_model, rename_config) 

673 

674 if "files" in rename_config: 

675 model = self._rename_files(model, rename_config["files"], target_files) 

676 

677 return model 

678 

679 def _apply_file_level_renaming( 

680 self, file_model: FileModel, rename_config: Dict[str, Any] 

681 ) -> None: 

682 """""" 

683 rename_operations = [ 

684 ("typedef", self._rename_typedefs), 

685 ("functions", self._rename_functions), 

686 ("macros", self._rename_macros), 

687 ("globals", self._rename_globals), 

688 ("includes", self._rename_includes), 

689 ("structs", self._rename_structs), 

690 ("enums", self._rename_enums), 

691 ("unions", self._rename_unions), 

692 ] 

693 

694 for config_key, rename_method in rename_operations: 

695 if config_key in rename_config: 

696 rename_method(file_model, rename_config[config_key]) 

697 

698 def _cleanup_type_references( 

699 self, model: ProjectModel, removed_typedef_patterns: List[str], target_files: Set[str] 

700 ) -> None: 

701 """""" 

702 self.logger.debug("Starting type reference cleanup with patterns: %s, target_files: %s", 

703 removed_typedef_patterns, list(target_files)) 

704 

705 if not removed_typedef_patterns: 

706 self.logger.debug("No typedef patterns to clean up") 

707 return 

708 

709 compiled_patterns = self._compile_patterns(removed_typedef_patterns) 

710 if not compiled_patterns: 

711 self.logger.debug("No valid compiled patterns") 

712 return 

713 

714 removed_types = set() 

715 

716 for file_path in target_files: 

717 if file_path in model.files: 

718 file_model = model.files[file_path] 

719 

720 for alias_name in list(file_model.aliases.keys()): 

721 if self._matches_any_pattern(alias_name, compiled_patterns): 

722 removed_types.add(alias_name) 

723 self.logger.debug("Found removed typedef: %s in file %s", alias_name, file_path) 

724 

725 self.logger.debug("Total removed types identified: %s", list(removed_types)) 

726 

727 cleaned_count = 0 

728 for file_path, file_model in model.files.items(): 

729 file_cleaned = 0 

730 

731 for func in file_model.functions: 

732 if func.return_type and self._contains_removed_type(func.return_type, removed_types): 

733 old_type = func.return_type 

734 func.return_type = self._remove_type_references(func.return_type, removed_types) 

735 if func.return_type != old_type: 

736 file_cleaned += 1 

737 self.logger.debug( 

738 "Cleaned return type '%s' -> '%s' in function %s", 

739 old_type, func.return_type, func.name 

740 ) 

741 

742 for param in func.parameters: 

743 if param.type and self._contains_removed_type(param.type, removed_types): 

744 old_type = param.type 

745 param.type = self._remove_type_references(param.type, removed_types) 

746 if param.type != old_type: 

747 file_cleaned += 1 

748 self.logger.debug( 

749 "Cleaned parameter type '%s' -> '%s' for parameter %s", 

750 old_type, param.type, param.name 

751 ) 

752 

753 for global_var in file_model.globals: 

754 if global_var.type and self._contains_removed_type(global_var.type, removed_types): 

755 old_type = global_var.type 

756 global_var.type = self._remove_type_references(global_var.type, removed_types) 

757 if global_var.type != old_type: 

758 file_cleaned += 1 

759 self.logger.debug( 

760 "Cleaned global variable type '%s' -> '%s' for %s", 

761 old_type, global_var.type, global_var.name 

762 ) 

763 

764 for struct in file_model.structs.values(): 

765 for field in struct.fields: 

766 if field.type and self._contains_removed_type(field.type, removed_types): 

767 old_type = field.type 

768 field.type = self._remove_type_references(field.type, removed_types) 

769 if field.type != old_type: 

770 file_cleaned += 1 

771 self.logger.debug( 

772 "Cleaned struct field type '%s' -> '%s' for %s.%s", 

773 old_type, field.type, struct.name, field.name 

774 ) 

775 

776 cleaned_count += file_cleaned 

777 

778 if cleaned_count > 0: 

779 self.logger.info( 

780 "Cleaned %d type references to removed typedefs: %s", 

781 cleaned_count, list(removed_types) 

782 ) 

783 

784 def _contains_removed_type(self, type_str: str, removed_types: Set[str]) -> bool: 

785 """""" 

786 if not type_str or not removed_types: 

787 return False 

788 

789 for removed_type in removed_types: 

790 if removed_type in type_str: 

791 return True 

792 return False 

793 

794 def _remove_type_references(self, type_str: str, removed_types: Set[str]) -> str: 

795 """""" 

796 if not type_str or not removed_types: 

797 return type_str 

798 

799 cleaned_type = type_str 

800 for removed_type in removed_types: 

801 if removed_type in cleaned_type: 

802 cleaned_type = cleaned_type.replace(removed_type, "void") 

803 

804 cleaned_type = " ".join(cleaned_type.split()) 

805 return cleaned_type 

806 

807 def _cleanup_type_references_by_names( 

808 self, model: ProjectModel, removed_typedef_names: Set[str] 

809 ) -> None: 

810 """""" 

811 if not removed_typedef_names: 

812 self.logger.debug("No removed typedef names provided") 

813 return 

814 

815 self.logger.debug("Cleaning type references for removed typedefs: %s", list(removed_typedef_names)) 

816 

817 cleaned_count = 0 

818 for file_path, file_model in model.files.items(): 

819 file_cleaned = 0 

820 

821 for func in file_model.functions: 

822 if func.return_type and self._contains_removed_type(func.return_type, removed_typedef_names): 

823 old_type = func.return_type 

824 func.return_type = self._remove_type_references(func.return_type, removed_typedef_names) 

825 if func.return_type != old_type: 

826 file_cleaned += 1 

827 self.logger.debug( 

828 "Cleaned return type '%s' -> '%s' in function %s", 

829 old_type, func.return_type, func.name 

830 ) 

831 

832 for param in func.parameters: 

833 if param.type and self._contains_removed_type(param.type, removed_typedef_names): 

834 old_type = param.type 

835 param.type = self._remove_type_references(param.type, removed_typedef_names) 

836 if param.type != old_type: 

837 file_cleaned += 1 

838 self.logger.debug( 

839 "Cleaned parameter type '%s' -> '%s' for parameter %s", 

840 old_type, param.type, param.name 

841 ) 

842 

843 for global_var in file_model.globals: 

844 if global_var.type and self._contains_removed_type(global_var.type, removed_typedef_names): 

845 old_type = global_var.type 

846 global_var.type = self._remove_type_references(global_var.type, removed_typedef_names) 

847 if global_var.type != old_type: 

848 file_cleaned += 1 

849 self.logger.debug( 

850 "Cleaned global variable type '%s' -> '%s' for %s", 

851 old_type, global_var.type, global_var.name 

852 ) 

853 

854 for struct in file_model.structs.values(): 

855 for field in struct.fields: 

856 if field.type and self._contains_removed_type(field.type, removed_typedef_names): 

857 old_type = field.type 

858 field.type = self._remove_type_references(field.type, removed_typedef_names) 

859 if field.type != old_type: 

860 file_cleaned += 1 

861 self.logger.debug( 

862 "Cleaned struct field type '%s' -> '%s' for %s.%s", 

863 old_type, field.type, struct.name, field.name 

864 ) 

865 

866 cleaned_count += file_cleaned 

867 if file_cleaned > 0: 

868 self.logger.debug("Cleaned %d type references in file %s", file_cleaned, file_path) 

869 

870 if cleaned_count > 0: 

871 self.logger.info( 

872 "Cleaned %d type references to removed typedefs: %s", 

873 cleaned_count, list(removed_typedef_names) 

874 ) 

875 else: 

876 self.logger.debug("No type references found to clean up") 

877 

878 def _update_type_references_for_renames(self, file_model: FileModel, typedef_renames: Dict[str, str]) -> None: 

879 """""" 

880 updated_count = 0 

881 

882 for func in file_model.functions: 

883 if func.return_type: 

884 old_type = func.return_type 

885 new_type = self._update_type_string_for_renames(func.return_type, typedef_renames) 

886 if new_type != old_type: 

887 func.return_type = new_type 

888 updated_count += 1 

889 self.logger.debug( 

890 "Updated return type '%s' -> '%s' in function %s", 

891 old_type, new_type, func.name 

892 ) 

893 

894 for param in func.parameters: 

895 if param.type: 

896 old_type = param.type 

897 new_type = self._update_type_string_for_renames(param.type, typedef_renames) 

898 if new_type != old_type: 

899 param.type = new_type 

900 updated_count += 1 

901 self.logger.debug( 

902 "Updated parameter type '%s' -> '%s' for parameter %s in function %s", 

903 old_type, new_type, param.name, func.name 

904 ) 

905 

906 for global_var in file_model.globals: 

907 if global_var.type: 

908 old_type = global_var.type 

909 new_type = self._update_type_string_for_renames(global_var.type, typedef_renames) 

910 if new_type != old_type: 

911 global_var.type = new_type 

912 updated_count += 1 

913 self.logger.debug( 

914 "Updated global variable type '%s' -> '%s' for %s", 

915 old_type, new_type, global_var.name 

916 ) 

917 

918 for struct in file_model.structs.values(): 

919 for field in struct.fields: 

920 if field.type: 

921 old_type = field.type 

922 new_type = self._update_type_string_for_renames(field.type, typedef_renames) 

923 if new_type != old_type: 

924 field.type = new_type 

925 updated_count += 1 

926 self.logger.debug( 

927 "Updated struct field type '%s' -> '%s' for %s.%s", 

928 old_type, new_type, struct.name, field.name 

929 ) 

930 

931 for union in file_model.unions.values(): 

932 for field in union.fields: 

933 if field.type: 

934 old_type = field.type 

935 new_type = self._update_type_string_for_renames(field.type, typedef_renames) 

936 if new_type != old_type: 

937 field.type = new_type 

938 updated_count += 1 

939 self.logger.debug( 

940 "Updated union field type '%s' -> '%s' for %s.%s", 

941 old_type, new_type, union.name, field.name 

942 ) 

943 

944 if updated_count > 0: 

945 self.logger.info( 

946 "Updated %d type references for renamed typedefs in %s: %s", 

947 updated_count, file_model.name, typedef_renames 

948 ) 

949 

950 def _update_type_string_for_renames(self, type_str: str, typedef_renames: Dict[str, str]) -> str: 

951 """""" 

952 if not type_str or not typedef_renames: 

953 return type_str 

954 

955 updated_type = type_str 

956 for old_name, new_name in typedef_renames.items(): 

957 pattern = r'\b' + re.escape(old_name) + r'\b' 

958 updated_type = re.sub(pattern, new_name, updated_type) 

959 

960 return updated_type 

961 

962 def _rename_dict_elements( 

963 self, 

964 elements_dict: Dict[str, Any], 

965 patterns_map: Dict[str, str], 

966 create_renamed_element: Callable[[str, Any], Any], 

967 element_type: str, 

968 file_name: str 

969 ) -> Dict[str, Any]: 

970 """""" 

971 original_count = len(elements_dict) 

972 seen_names = set() 

973 deduplicated_elements = {} 

974 

975 for name, element in elements_dict.items(): 

976 new_name = self._apply_rename_patterns(name, patterns_map) 

977 

978 if new_name in seen_names: 

979 self.logger.debug( 

980 "Deduplicating %s: removing duplicate '%s' (renamed from '%s')", 

981 element_type, new_name, name 

982 ) 

983 continue 

984 

985 seen_names.add(new_name) 

986 

987 updated_element = element if new_name == name else create_renamed_element(new_name, element) 

988 deduplicated_elements[new_name] = updated_element 

989 

990 removed_count = original_count - len(deduplicated_elements) 

991 if removed_count > 0: 

992 self.logger.info( 

993 "Renamed %ss in %s, removed %d duplicates", element_type, file_name, removed_count 

994 ) 

995 

996 return deduplicated_elements 

997 

998 def _rename_list_elements( 

999 self, 

1000 elements_list: List[Any], 

1001 patterns_map: Dict[str, str], 

1002 get_element_name: Callable[[Any], str], 

1003 create_renamed_element: Callable[[str, Any], Any], 

1004 element_type: str, 

1005 file_name: str 

1006 ) -> List[Any]: 

1007 """""" 

1008 original_count = len(elements_list) 

1009 seen_names = set() 

1010 deduplicated_elements = [] 

1011 

1012 for element in elements_list: 

1013 name = get_element_name(element) 

1014 new_name = self._apply_rename_patterns(name, patterns_map) 

1015 

1016 if new_name in seen_names: 

1017 self.logger.debug( 

1018 "Deduplicating %s: removing duplicate '%s' (renamed from '%s')", 

1019 element_type, new_name, name 

1020 ) 

1021 continue 

1022 

1023 seen_names.add(new_name) 

1024 

1025 updated_element = element if new_name == name else create_renamed_element(new_name, element) 

1026 deduplicated_elements.append(updated_element) 

1027 

1028 removed_count = original_count - len(deduplicated_elements) 

1029 if removed_count > 0: 

1030 self.logger.info( 

1031 "Renamed %ss in %s, removed %d duplicates", element_type, file_name, removed_count 

1032 ) 

1033 

1034 return deduplicated_elements 

1035 

1036 def _apply_rename_patterns(self, original_name: str, patterns_map: Dict[str, str]) -> str: 

1037 """""" 

1038 for pattern, replacement in patterns_map.items(): 

1039 try: 

1040 new_name = re.sub(pattern, replacement, original_name) 

1041 if new_name != original_name: 

1042 self.logger.debug( 

1043 "Renamed '%s' to '%s' using pattern '%s'", 

1044 original_name, new_name, pattern 

1045 ) 

1046 return new_name 

1047 except re.error as e: 

1048 self.logger.warning( 

1049 "Invalid regex pattern '%s': %s", pattern, e 

1050 ) 

1051 continue 

1052 

1053 return original_name 

1054 

1055 def _rename_typedefs(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1056 """""" 

1057 if not patterns_map: 

1058 return 

1059 

1060 typedef_renames = {} 

1061 

1062 def create_renamed_alias(name: str, alias: Alias) -> Alias: 

1063 return Alias(name, alias.original_type, alias.uses) 

1064 

1065 for old_name in file_model.aliases: 

1066 new_name = self._apply_rename_patterns(old_name, patterns_map) 

1067 if new_name != old_name: 

1068 typedef_renames[old_name] = new_name 

1069 

1070 file_model.aliases = self._rename_dict_elements( 

1071 file_model.aliases, patterns_map, create_renamed_alias, "typedef", file_model.name 

1072 ) 

1073 

1074 if typedef_renames: 

1075 self._update_type_references_for_renames(file_model, typedef_renames) 

1076 

1077 def _rename_functions(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1078 """""" 

1079 if not patterns_map: 

1080 return 

1081 

1082 def get_function_name(func: Function) -> str: 

1083 return func.name 

1084 

1085 def create_renamed_function(name: str, func: Function) -> Function: 

1086 return Function(name, func.return_type, func.parameters, func.is_static, func.is_declaration, func.is_inline) 

1087 

1088 file_model.functions = self._rename_list_elements( 

1089 file_model.functions, patterns_map, get_function_name, 

1090 create_renamed_function, "function", file_model.name 

1091 ) 

1092 

1093 def _rename_macros(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1094 """""" 

1095 if not patterns_map: 

1096 return 

1097 

1098 def get_macro_name(macro: str) -> str: 

1099 import re 

1100 if macro.startswith("#define "): 

1101 match = re.search(r"#define\s+([A-Za-z_][A-Za-z0-9_]*)", macro) 

1102 if match: 

1103 return match.group(1) 

1104 return macro 

1105 

1106 def create_renamed_macro(name: str, macro: str) -> str: 

1107 import re 

1108 if macro.startswith("#define "): 

1109 pattern = r"(#define\s+)([A-Za-z_][A-Za-z0-9_]*)(\s*\([^)]*\))?(.*)?" 

1110 match = re.match(pattern, macro) 

1111 if match: 

1112 define_part = match.group(1) 

1113 params = match.group(3) or "" 

1114 rest = match.group(4) or "" 

1115 return f"{define_part}{name}{params}{rest}" 

1116 return macro 

1117 

1118 file_model.macros = self._rename_list_elements( 

1119 file_model.macros, patterns_map, get_macro_name, 

1120 create_renamed_macro, "macro", file_model.name 

1121 ) 

1122 

1123 def _rename_globals(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1124 """""" 

1125 if not patterns_map: 

1126 return 

1127 

1128 def get_global_name(global_var: Field) -> str: 

1129 return global_var.name 

1130 

1131 def create_renamed_global(name: str, global_var: Field) -> Field: 

1132 return Field(name, global_var.type) 

1133 

1134 file_model.globals = self._rename_list_elements( 

1135 file_model.globals, patterns_map, get_global_name, 

1136 create_renamed_global, "global", file_model.name 

1137 ) 

1138 

1139 def _rename_includes(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1140 """""" 

1141 if not patterns_map: 

1142 return 

1143 

1144 file_model.includes = self._rename_set_elements( 

1145 file_model.includes, patterns_map, "include", file_model.name 

1146 ) 

1147 

1148 file_model.include_relations = self._rename_include_relations( 

1149 file_model.include_relations, patterns_map 

1150 ) 

1151 

1152 def _rename_set_elements( 

1153 self, 

1154 elements_set: Set[str], 

1155 patterns_map: Dict[str, str], 

1156 element_type: str, 

1157 file_name: str 

1158 ) -> Set[str]: 

1159 """""" 

1160 original_count = len(elements_set) 

1161 seen_names = set() 

1162 deduplicated_elements = set() 

1163 

1164 for element in elements_set: 

1165 new_name = self._apply_rename_patterns(element, patterns_map) 

1166 

1167 if new_name in seen_names: 

1168 self.logger.debug( 

1169 "Deduplicating %s: removing duplicate '%s' (renamed from '%s')", 

1170 element_type, new_name, element 

1171 ) 

1172 continue 

1173 

1174 seen_names.add(new_name) 

1175 deduplicated_elements.add(new_name) 

1176 

1177 removed_count = original_count - len(deduplicated_elements) 

1178 if removed_count > 0: 

1179 self.logger.info( 

1180 "Renamed %ss in %s, removed %d duplicates", element_type, file_name, removed_count 

1181 ) 

1182 

1183 return deduplicated_elements 

1184 

1185 def _rename_include_relations( 

1186 self, relations: List[IncludeRelation], patterns_map: Dict[str, str] 

1187 ) -> List[IncludeRelation]: 

1188 """""" 

1189 updated_relations = [] 

1190 for relation in relations: 

1191 new_included_file = self._apply_rename_patterns(relation.included_file, patterns_map) 

1192 updated_relation = IncludeRelation( 

1193 relation.source_file, 

1194 new_included_file, 

1195 relation.depth 

1196 ) 

1197 updated_relations.append(updated_relation) 

1198 return updated_relations 

1199 

1200 def _rename_structs(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1201 """""" 

1202 if not patterns_map: 

1203 return 

1204 

1205 def create_renamed_struct(name: str, struct: Struct) -> Struct: 

1206 return Struct(name, struct.fields) 

1207 

1208 file_model.structs = self._rename_dict_elements( 

1209 file_model.structs, patterns_map, create_renamed_struct, "struct", file_model.name 

1210 ) 

1211 

1212 def _rename_enums(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1213 """""" 

1214 if not patterns_map: 

1215 return 

1216 

1217 def create_renamed_enum(name: str, enum: Enum) -> Enum: 

1218 return Enum(name, enum.values) 

1219 

1220 file_model.enums = self._rename_dict_elements( 

1221 file_model.enums, patterns_map, create_renamed_enum, "enum", file_model.name 

1222 ) 

1223 

1224 def _rename_unions(self, file_model: FileModel, patterns_map: Dict[str, str]) -> None: 

1225 """""" 

1226 if not patterns_map: 

1227 return 

1228 

1229 def create_renamed_union(name: str, union: Union) -> Union: 

1230 return Union(name, union.fields) 

1231 

1232 file_model.unions = self._rename_dict_elements( 

1233 file_model.unions, patterns_map, create_renamed_union, "union", file_model.name 

1234 ) 

1235 

1236 def _rename_files(self, model: ProjectModel, patterns_map: Dict[str, str], target_files: Set[str]) -> ProjectModel: 

1237 """""" 

1238 if not patterns_map: 

1239 return model 

1240 

1241 updated_files = {} 

1242 file_rename_map: Dict[str, str] = {} 

1243 

1244 for file_path, file_model in model.files.items(): 

1245 if file_path in target_files: 

1246 new_file_path = self._apply_rename_patterns(file_path, patterns_map) 

1247 

1248 if new_file_path != file_path: 

1249 file_model.name = new_file_path 

1250 file_rename_map[Path(file_path).name] = Path(new_file_path).name 

1251 self.logger.debug("Renamed file: %s -> %s", file_path, new_file_path) 

1252 

1253 updated_files[new_file_path] = file_model 

1254 else: 

1255 updated_files[file_path] = file_model 

1256 

1257 model.files = updated_files 

1258 

1259 if file_rename_map: 

1260 for fm in model.files.values(): 

1261 if fm.includes: 

1262 new_includes: Set[str] = set() 

1263 for inc in fm.includes: 

1264 inc_new = file_rename_map.get(inc, self._apply_rename_patterns(inc, patterns_map)) 

1265 new_includes.add(inc_new) 

1266 fm.includes = new_includes 

1267 

1268 if fm.include_relations: 

1269 for rel in fm.include_relations: 

1270 src_new = file_rename_map.get(rel.source_file, self._apply_rename_patterns(rel.source_file, patterns_map)) 

1271 inc_new = file_rename_map.get(rel.included_file, self._apply_rename_patterns(rel.included_file, patterns_map)) 

1272 rel.source_file = src_new 

1273 rel.included_file = inc_new 

1274 

1275 return model 

1276 

1277 def _apply_additions( 

1278 self, model: ProjectModel, add_config: Dict[str, Any], target_files: Set[str] 

1279 ) -> ProjectModel: 

1280 """""" 

1281 self.logger.debug( 

1282 "Applying addition transformations to %d files", len(target_files) 

1283 ) 

1284 

1285 for file_path in target_files: 

1286 if file_path in model.files: 

1287 self.logger.debug("Applying additions to file: %s", file_path) 

1288 

1289 return model 

1290 

1291 def _apply_removals( 

1292 self, model: ProjectModel, remove_config: Dict[str, Any], target_files: Set[str] 

1293 ) -> ProjectModel: 

1294 """""" 

1295 self.logger.debug( 

1296 "Applying removal transformations to %d files", len(target_files) 

1297 ) 

1298 

1299 for file_path in target_files: 

1300 if file_path in model.files: 

1301 file_model = model.files[file_path] 

1302 self.logger.debug("Applying removals to file: %s", file_path) 

1303 self._apply_file_level_removals(file_model, remove_config) 

1304 

1305 return model 

1306 

1307 def _apply_file_level_removals( 

1308 self, file_model: FileModel, remove_config: Dict[str, Any] 

1309 ) -> None: 

1310 """""" 

1311 removal_operations = [ 

1312 ("typedef", self._remove_typedefs), 

1313 ("functions", self._remove_functions), 

1314 ("macros", self._remove_macros), 

1315 ("globals", self._remove_globals), 

1316 ("includes", self._remove_includes), 

1317 ("structs", self._remove_structs), 

1318 ("enums", self._remove_enums), 

1319 ("unions", self._remove_unions), 

1320 ] 

1321 

1322 for config_key, removal_method in removal_operations: 

1323 if config_key in remove_config: 

1324 removal_method(file_model, remove_config[config_key]) 

1325 

1326 def _remove_dict_elements( 

1327 self, 

1328 elements_dict: Dict[str, Any], 

1329 patterns: List[str], 

1330 element_type: str, 

1331 file_name: str 

1332 ) -> Dict[str, Any]: 

1333 """""" 

1334 if not patterns: 

1335 return elements_dict 

1336 

1337 original_count = len(elements_dict) 

1338 compiled_patterns = self._compile_patterns(patterns) 

1339 

1340 filtered_elements = {} 

1341 for name, element in elements_dict.items(): 

1342 if not self._matches_any_pattern(name, compiled_patterns): 

1343 filtered_elements[name] = element 

1344 else: 

1345 self.logger.debug("Removed %s: %s", element_type, name) 

1346 

1347 removed_count = original_count - len(filtered_elements) 

1348 if removed_count > 0: 

1349 self.logger.info( 

1350 "Removed %d %ss from %s", removed_count, element_type, file_name 

1351 ) 

1352 

1353 return filtered_elements 

1354 

1355 def _remove_list_elements( 

1356 self, 

1357 elements_list: List[Any], 

1358 patterns: List[str], 

1359 get_element_name: Callable[[Any], str], 

1360 element_type: str, 

1361 file_name: str 

1362 ) -> List[Any]: 

1363 """""" 

1364 if not patterns: 

1365 return elements_list 

1366 

1367 original_count = len(elements_list) 

1368 compiled_patterns = self._compile_patterns(patterns) 

1369 

1370 filtered_elements = [] 

1371 for element in elements_list: 

1372 name = get_element_name(element) 

1373 if not self._matches_any_pattern(name, compiled_patterns): 

1374 filtered_elements.append(element) 

1375 else: 

1376 self.logger.debug("Removed %s: %s", element_type, name) 

1377 

1378 removed_count = original_count - len(filtered_elements) 

1379 if removed_count > 0: 

1380 self.logger.info( 

1381 "Removed %d %ss from %s", removed_count, element_type, file_name 

1382 ) 

1383 

1384 return filtered_elements 

1385 

1386 def _remove_typedefs(self, file_model: FileModel, patterns: List[str]) -> None: 

1387 """""" 

1388 file_model.aliases = self._remove_dict_elements( 

1389 file_model.aliases, patterns, "typedef", file_model.name 

1390 ) 

1391 

1392 def _remove_functions(self, file_model: FileModel, patterns: List[str]) -> None: 

1393 """""" 

1394 def get_function_name(func: Function) -> str: 

1395 return func.name 

1396 

1397 file_model.functions = self._remove_list_elements( 

1398 file_model.functions, patterns, get_function_name, "function", file_model.name 

1399 ) 

1400 

1401 def _remove_macros(self, file_model: FileModel, patterns: List[str]) -> None: 

1402 """""" 

1403 def get_macro_name(macro: str) -> str: 

1404 import re 

1405 if macro.startswith("#define "): 

1406 match = re.search(r"#define\s+([A-Za-z_][A-Za-z0-9_]*)", macro) 

1407 if match: 

1408 return match.group(1) 

1409 return macro 

1410 

1411 file_model.macros = self._remove_list_elements( 

1412 file_model.macros, patterns, get_macro_name, "macro", file_model.name 

1413 ) 

1414 

1415 def _remove_globals(self, file_model: FileModel, patterns: List[str]) -> None: 

1416 """""" 

1417 def get_global_name(global_var: Field) -> str: 

1418 return global_var.name 

1419 

1420 file_model.globals = self._remove_list_elements( 

1421 file_model.globals, patterns, get_global_name, "global variable", file_model.name 

1422 ) 

1423 

1424 def _remove_includes(self, file_model: FileModel, patterns: List[str]) -> None: 

1425 """""" 

1426 if not patterns: 

1427 return 

1428 

1429 original_count = len(file_model.includes) 

1430 compiled_patterns = self._compile_patterns(patterns) 

1431 

1432 filtered_includes = set() 

1433 for include in file_model.includes: 

1434 if not self._matches_any_pattern(include, compiled_patterns): 

1435 filtered_includes.add(include) 

1436 else: 

1437 self.logger.debug("Removed include: %s", include) 

1438 

1439 file_model.includes = filtered_includes 

1440 removed_count = original_count - len(file_model.includes) 

1441 

1442 if removed_count > 0: 

1443 self._remove_matching_include_relations(file_model, compiled_patterns, removed_count) 

1444 

1445 def _remove_matching_include_relations( 

1446 self, file_model: FileModel, compiled_patterns: List[Pattern[str]], removed_includes_count: int 

1447 ) -> None: 

1448 """""" 

1449 original_relations_count = len(file_model.include_relations) 

1450 filtered_relations = [] 

1451 

1452 for relation in file_model.include_relations: 

1453 if not self._matches_any_pattern(relation.included_file, compiled_patterns): 

1454 filtered_relations.append(relation) 

1455 else: 

1456 self.logger.debug("Removed include relation: %s -> %s", 

1457 relation.source_file, relation.included_file) 

1458 

1459 file_model.include_relations = filtered_relations 

1460 removed_relations_count = original_relations_count - len(file_model.include_relations) 

1461 

1462 self.logger.info( 

1463 "Removed %d includes and %d include relations from %s", 

1464 removed_includes_count, removed_relations_count, file_model.name 

1465 ) 

1466 

1467 def _remove_structs(self, file_model: FileModel, patterns: List[str]) -> None: 

1468 """""" 

1469 file_model.structs = self._remove_dict_elements( 

1470 file_model.structs, patterns, "struct", file_model.name 

1471 ) 

1472 

1473 def _remove_enums(self, file_model: FileModel, patterns: List[str]) -> None: 

1474 """""" 

1475 file_model.enums = self._remove_dict_elements( 

1476 file_model.enums, patterns, "enum", file_model.name 

1477 ) 

1478 

1479 def _remove_unions(self, file_model: FileModel, patterns: List[str]) -> None: 

1480 """""" 

1481 file_model.unions = self._remove_dict_elements( 

1482 file_model.unions, patterns, "union", file_model.name 

1483 ) 

1484 

1485 def _should_include_file( 

1486 self, 

1487 file_path: str, 

1488 include_patterns: List[Pattern[str]], 

1489 exclude_patterns: List[Pattern[str]], 

1490 ) -> bool: 

1491 """""" 

1492 if include_patterns: 

1493 if not any(pattern.search(file_path) for pattern in include_patterns): 

1494 return False 

1495 

1496 if exclude_patterns: 

1497 if any(pattern.search(file_path) for pattern in exclude_patterns): 

1498 return False 

1499 

1500 return True 

1501 

1502 def _compile_patterns(self, patterns: List[str]) -> List[Pattern[str]]: 

1503 """""" 

1504 compiled_patterns: List[Pattern[str]] = [] 

1505 for pattern in patterns: 

1506 try: 

1507 compiled_patterns.append(re.compile(pattern)) 

1508 except re.error as e: 

1509 self.logger.warning("Invalid regex pattern '%s': %s", pattern, e) 

1510 return compiled_patterns 

1511 

1512 # Removed unused _filter_dict (not used in current pipeline) 

1513 

1514 # Removed unused _filter_list (not used in current pipeline) 

1515 

1516 # Removed unused _dict_to_file_model (no callers in current code) 

1517 

1518 def _save_model(self, model: ProjectModel, output_file: str) -> None: 

1519 """""" 

1520 try: 

1521 model.save(output_file) 

1522 self.logger.debug("Model saved to: %s", output_file) 

1523 except Exception as e: 

1524 raise ValueError(f"Failed to save model to {output_file}: {e}") from e 

1525 

1526 

1527