glam/scripts/fix_slot_imports.py

145 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""
Fix missing slot imports in class files.
This script scans class files for slots used in the `slots:` list
and adds missing imports for those slots.
"""
import os
import re
from pathlib import Path
import yaml
SCHEMA_DIR = Path('/Users/kempersc/apps/glam/schemas/20251121/linkml')
CLASSES_DIR = SCHEMA_DIR / 'modules' / 'classes'
SLOTS_DIR = SCHEMA_DIR / 'modules' / 'slots'
# Get all available slot files
available_slots = {f.stem for f in SLOTS_DIR.glob('*.yaml')}
print(f"Available slot files: {len(available_slots)}")
def get_class_slots(class_file: Path) -> set:
"""Extract slot names from a class file's slots: list."""
with open(class_file) as f:
content = f.read()
# Find slots used in classes
slots = set()
# Pattern to find slots: section under classes
# This is tricky because we need to parse YAML structure
try:
data = yaml.safe_load(content)
if data and 'classes' in data:
for class_name, class_def in data['classes'].items():
if class_def and 'slots' in class_def:
for slot in class_def['slots']:
slots.add(slot)
except yaml.YAMLError as e:
print(f"YAML error in {class_file}: {e}")
return set()
return slots
def get_imported_slots(class_file: Path) -> set:
"""Extract slot imports from a class file."""
with open(class_file) as f:
content = f.read()
imported = set()
# Match imports like: - ../slots/slot_name
pattern = r'-\s*\.\./slots/(\w+)'
for match in re.finditer(pattern, content):
imported.add(match.group(1))
return imported
def add_slot_imports(class_file: Path, slots_to_add: set):
"""Add missing slot imports to a class file."""
with open(class_file) as f:
content = f.read()
# Find the imports section
lines = content.split('\n')
new_lines = []
in_imports = False
imports_ended = False
added = False
for i, line in enumerate(lines):
new_lines.append(line)
# Detect imports section
if line.strip() == 'imports:':
in_imports = True
continue
if in_imports and not imports_ended:
# Check if we're still in imports (lines starting with -)
if line.strip().startswith('-'):
continue
else:
# End of imports section - add our slots before this line
imports_ended = True
# Insert slot imports before the current line
for slot in sorted(slots_to_add):
new_lines.insert(len(new_lines) - 1, f'- ../slots/{slot}')
added = True
# If we didn't find a good place, add after last import
if not added:
# Find imports section and add at the end
result_lines = []
in_imports = False
last_import_idx = -1
for i, line in enumerate(lines):
result_lines.append(line)
if line.strip() == 'imports:':
in_imports = True
elif in_imports and line.strip().startswith('-'):
last_import_idx = len(result_lines)
elif in_imports and not line.strip().startswith('-') and line.strip() != '':
in_imports = False
if last_import_idx > 0:
# Insert after last import
for slot in sorted(slots_to_add, reverse=True):
result_lines.insert(last_import_idx, f'- ../slots/{slot}')
new_lines = result_lines
with open(class_file, 'w') as f:
f.write('\n'.join(new_lines))
def main():
total_fixed = 0
files_modified = 0
for class_file in sorted(CLASSES_DIR.glob('*.yaml')):
# Get slots used by this class
used_slots = get_class_slots(class_file)
if not used_slots:
continue
# Get already imported slots
imported_slots = get_imported_slots(class_file)
# Find slots that need to be imported
missing_imports = used_slots - imported_slots
# Filter to only slots that have files
valid_missing = missing_imports & available_slots
if valid_missing:
print(f"{class_file.name}: Adding {len(valid_missing)} slot imports")
add_slot_imports(class_file, valid_missing)
total_fixed += len(valid_missing)
files_modified += 1
print(f"\n=== Summary ===")
print(f"Files modified: {files_modified}")
print(f"Total imports added: {total_fixed}")
if __name__ == '__main__':
main()