#!/usr/bin/env python3 """ Deep Code Scanner for DolinaSmrti Analyzes all code for: - Missing asset references - Broken path links - Naming inconsistencies - Optimization opportunities """ import os import re import json from pathlib import Path from typing import Dict, List, Set, Tuple PROJECT_ROOT = Path(__file__).parent.parent SRC_DIR = PROJECT_ROOT / "src" ASSETS_DIR = PROJECT_ROOT / "assets" class CodeScanner: def __init__(self): self.errors = [] self.warnings = [] self.suggestions = [] self.asset_paths = set() self.code_references = set() def scan_all(self): """Run complete code scan""" print("šŸ” Starting Deep Code Scan...") print("="*60) # 1. Collect all asset paths print("\nšŸ“ Scanning asset files...") self.collect_asset_paths() # 2. Scan code for references print("\nšŸ’» Scanning code references...") self.scan_code_references() # 3. Validate paths print("\nāœ… Validating paths...") self.validate_paths() # 4. Check naming conventions print("\nšŸ·ļø Checking naming conventions...") self.check_naming_conventions() # 5. Optimize suggestions print("\n⚔ Analyzing optimization opportunities...") self.find_optimizations() # 6. Generate report print("\nšŸ“Š Generating report...") self.generate_report() def collect_asset_paths(self): """Collect all asset file paths""" for ext in ['*.png', '*.jpg', '*.jpeg', '*.webp']: for file in ASSETS_DIR.rglob(ext): rel_path = file.relative_to(PROJECT_ROOT) self.asset_paths.add(str(rel_path)) print(f" Found {len(self.asset_paths)} asset files") def scan_code_references(self): """Scan all code files for asset references""" patterns = [ r'["\']([^"\']*\.png)["\']', # PNG files r'["\']([^"\']*\.jpg)["\']', # JPG files r'["\']([^"\']*\.webp)["\']', # WEBP files r'assets/([^"\']*)', # Asset paths ] code_files = [] code_files.extend(SRC_DIR.rglob("*.js")) code_files.extend(SRC_DIR.rglob("*.html")) code_files.extend((PROJECT_ROOT / "scripts").rglob("*.py")) for file in code_files: try: content = file.read_text(encoding='utf-8') for pattern in patterns: matches = re.findall(pattern, content) for match in matches: self.code_references.add(match) except Exception as e: self.warnings.append(f"Could not read {file}: {e}") print(f" Found {len(self.code_references)} asset references in code") def validate_paths(self): """Check if all referenced assets exist""" broken_refs = [] for ref in self.code_references: # Try to find matching asset found = False for asset_path in self.asset_paths: if ref in asset_path or asset_path.endswith(ref): found = True break if not found: # Check if file actually exists possible_paths = [ PROJECT_ROOT / ref, PROJECT_ROOT / "assets" / ref, PROJECT_ROOT / "assets" / "images" / ref, ] exists = any(p.exists() for p in possible_paths) if not exists: broken_refs.append(ref) if broken_refs: self.errors.append({ 'type': 'BROKEN_REFERENCES', 'count': len(broken_refs), 'items': broken_refs[:10] # Show first 10 }) print(f" āŒ Found {len(broken_refs)} broken references") else: print(f" āœ… All asset references valid") def check_naming_conventions(self): """Check if assets follow naming conventions""" issues = [] for asset_path in self.asset_paths: filename = Path(asset_path).name # Check for timestamps in name if re.search(r'_\d{13,}', filename): issues.append(f"Timestamp in name: {filename}") # Check for Style 32 convention if 'STYLE_32' in asset_path and 'style32' not in filename.lower(): issues.append(f"Missing style32 suffix: {filename}") # Check for proper prefixes if not any(filename.startswith(prefix) for prefix in [ 'terrain', 'interior', 'npc', 'liki', 'zgradbe', 'oprema', 'narava', 'vmesnik', 'uploaded' ]): issues.append(f"No category prefix: {filename}") if issues: self.warnings.append({ 'type': 'NAMING_ISSUES', 'count': len(issues), 'items': issues[:15] }) print(f" āš ļø Found {len(issues)} naming issues") else: print(f" āœ… All names follow conventions") def find_optimizations(self): """Find optimization opportunities""" suggestions = [] # Check for duplicate file sizes (possible duplicates) size_map = {} for asset_path in self.asset_paths: full_path = PROJECT_ROOT / asset_path if full_path.exists(): size = full_path.stat().st_size if size in size_map: size_map[size].append(asset_path) else: size_map[size] = [asset_path] duplicates = {k: v for k, v in size_map.items() if len(v) > 1} if duplicates: suggestions.append(f"Found {len(duplicates)} groups of same-size files (possible duplicates)") # Check for large files (>1MB) large_files = [] for asset_path in self.asset_paths: full_path = PROJECT_ROOT / asset_path if full_path.exists(): size_mb = full_path.stat().st_size / (1024 * 1024) if size_mb > 1: large_files.append((asset_path, f"{size_mb:.2f} MB")) if large_files: suggestions.append(f"Found {len(large_files)} files > 1MB (consider compression)") if suggestions: self.suggestions.append({ 'type': 'OPTIMIZATIONS', 'items': suggestions }) print(f" šŸ’” {len(suggestions)} optimization suggestions") else: print(f" āœ… No optimization needed") def generate_report(self): """Generate comprehensive report""" report = { 'scan_date': '2026-01-04T19:15:00+01:00', 'total_assets': len(self.asset_paths), 'total_references': len(self.code_references), 'errors': self.errors, 'warnings': self.warnings, 'suggestions': self.suggestions, 'summary': { 'status': 'PASS' if not self.errors else 'FAIL', 'error_count': len(self.errors), 'warning_count': len(self.warnings), 'suggestion_count': len(self.suggestions) } } # Save report report_path = PROJECT_ROOT / "docs" / "CODE_SCAN_REPORT.json" with open(report_path, 'w', encoding='utf-8') as f: json.dump(report, f, indent=2, ensure_ascii=False) # Print summary print("\n" + "="*60) print("šŸ“Š SCAN SUMMARY") print("="*60) print(f"Total Assets: {len(self.asset_paths)}") print(f"Code References: {len(self.code_references)}") print(f"Errors: {len(self.errors)}") print(f"Warnings: {len(self.warnings)}") print(f"Suggestions: {len(self.suggestions)}") print(f"\nStatus: {'āœ… PASS' if not self.errors else 'āŒ FAIL'}") print(f"\nšŸ“„ Full report: {report_path}") # Print details if self.errors: print("\nāŒ ERRORS:") for error in self.errors: print(f" {error['type']}: {error['count']} issues") for item in error.get('items', [])[:5]: print(f" - {item}") if self.warnings: print("\nāš ļø WARNINGS:") for warning in self.warnings: if isinstance(warning, dict): print(f" {warning['type']}: {warning['count']} issues") else: print(f" {warning}") if self.suggestions: print("\nšŸ’” SUGGESTIONS:") for suggestion in self.suggestions: for item in suggestion.get('items', []): print(f" {item}") def main(): scanner = CodeScanner() scanner.scan_all() if __name__ == "__main__": main()