glam/scripts/fix_slot_imports_v2.py

124 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""
Fix missing slot imports in class files - Version 2.
This script properly handles YAML indentation when adding imports.
"""
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(content: str) -> set:
"""Extract slot names from a class file's slots: list."""
slots = set()
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:
return set()
return slots
def get_imported_slots(content: str) -> set:
"""Extract slot imports from a class file."""
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(content: str, slots_to_add: set) -> str:
"""Add missing slot imports to content, preserving YAML structure."""
lines = content.split('\n')
result_lines = []
in_imports = False
imports_indent = ''
last_import_line_idx = -1
for i, line in enumerate(lines):
result_lines.append(line)
# Detect start of imports section
if re.match(r'^imports:\s*$', line):
in_imports = True
continue
# Inside imports section
if in_imports:
# Check if this is an import line (starts with - after whitespace)
match = re.match(r'^(\s*)-\s*', line)
if match:
imports_indent = match.group(1)
last_import_line_idx = len(result_lines)
elif line.strip() and not line.strip().startswith('#'):
# Non-empty, non-comment line that's not an import - we've left imports section
in_imports = False
# Insert new imports after the last import line
# Note: imports_indent can be '' (empty string) which is falsy but valid
if last_import_line_idx > 0:
# Create import lines with correct indentation
new_imports = [f"{imports_indent}- ../slots/{slot}" for slot in sorted(slots_to_add)]
# Insert after last import
for new_import in reversed(new_imports):
result_lines.insert(last_import_line_idx, new_import)
return '\n'.join(result_lines)
def main():
total_fixed = 0
files_modified = 0
errors = 0
for class_file in sorted(CLASSES_DIR.glob('*.yaml')):
content = class_file.read_text()
# Get slots used by this class
used_slots = get_class_slots(content)
if not used_slots:
continue
# Get already imported slots
imported_slots = get_imported_slots(content)
# 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:
new_content = add_slot_imports(content, valid_missing)
# Verify the new content is valid YAML
try:
yaml.safe_load(new_content)
class_file.write_text(new_content)
print(f"OK: {class_file.name}: Added {len(valid_missing)} imports")
total_fixed += len(valid_missing)
files_modified += 1
except yaml.YAMLError as e:
print(f"ERROR: {class_file.name}: YAML error after modification: {e}")
errors += 1
print(f"\n=== Summary ===")
print(f"Files modified: {files_modified}")
print(f"Total imports added: {total_fixed}")
print(f"Errors: {errors}")
if __name__ == '__main__':
main()