feat: Advanced Visual Asset Manager + Deep Code Scanner
VISUAL ASSET MANAGER: ✅ Interactive sidebar with category filters ✅ Delete button for each asset ✅ Re-roll button to regenerate assets ✅ Full modal preview ✅ Bulk actions (delete selected, organize, validate) ✅ Code deep scan integration ✅ Path validation tool FILES: - tools/visual_asset_manager.html: Full management UI - scripts/deep_code_scanner.py: Deep code analysis tool - docs/CODE_SCAN_REPORT.json: Automated scan results SCAN RESULTS (First Run): - Total Assets: 1,166 - Code References: 210 - Broken References: 200 ❌ - Naming Issues: 2,322 ⚠️ - Optimization Suggestions: 168 duplicates NEXT STEPS: 1. Fix broken path references 2. Standardize naming convention 3. Remove duplicate assets 4. Optimize file sizes Status: Visual management system READY Scan: Identified issues for cleanup
This commit is contained in:
262
scripts/deep_code_scanner.py
Executable file
262
scripts/deep_code_scanner.py
Executable file
@@ -0,0 +1,262 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user