164 lines
5.7 KiB
Python
164 lines
5.7 KiB
Python
"""Kit manifest loading, slot inheritance, and which slots are worth showing."""
|
|
|
|
import os
|
|
import sys
|
|
from tkinter import messagebox
|
|
import xml.etree.ElementTree as ET
|
|
|
|
import app_state
|
|
|
|
|
|
def read_summary_facet(slot_element):
|
|
"""Reads a slot's '@summary' facet. Absent means summary, matching manifest convention."""
|
|
facets = slot_element.find("facets")
|
|
if facets is None:
|
|
return True
|
|
for bool_facet in facets.findall("bool"):
|
|
if bool_facet.get("name") == "summary":
|
|
return (bool_facet.get("val", "true").strip().lower() != "false")
|
|
return True
|
|
|
|
|
|
def get_slots_with_inheritance(type_name, kit_schemas):
|
|
"""Recursively resolves the slots for a given type by traversing its base chain."""
|
|
if type_name not in kit_schemas:
|
|
kit_prefix = type_name.split("::")[0] if "::" in type_name else type_name
|
|
messagebox.showerror("Schema Warning", f"Kit {kit_prefix} is not in Schema selection!")
|
|
sys.exit(1)
|
|
|
|
type_element = kit_schemas[type_name]
|
|
base_type = type_element.get("base")
|
|
|
|
resolved_slots = []
|
|
if base_type:
|
|
resolved_slots = get_slots_with_inheritance(base_type, kit_schemas)
|
|
|
|
current_slots = []
|
|
for slot in type_element.findall("slot"):
|
|
current_slots.append({
|
|
"name": slot.get("name"),
|
|
"type": slot.get("type"),
|
|
"default": slot.get("default", "false"),
|
|
"flags": slot.get("flags", ""), # Capture visibility flags ('o' maps to operator/hidden)
|
|
"summary": read_summary_facet(slot),
|
|
"declared_id": int(slot.get("id"))
|
|
})
|
|
|
|
current_slots.sort(key=lambda s: s["declared_id"])
|
|
resolved_slots.extend(current_slots)
|
|
return resolved_slots
|
|
|
|
|
|
def compile_schema_registry(manifest_paths):
|
|
"""Loads and maps types across multiple kit files, ensuring all dependencies exist."""
|
|
kit_schemas = {}
|
|
|
|
for path in manifest_paths:
|
|
try:
|
|
tree = ET.parse(path)
|
|
root = tree.getroot()
|
|
kit_name = root.get("name")
|
|
|
|
for type_tag in root.findall("type"):
|
|
type_name = f"{kit_name}::{type_tag.get('name')}"
|
|
kit_schemas[type_name] = type_tag
|
|
except Exception as e:
|
|
print(f"Error parsing manifest {path}: {e}")
|
|
|
|
app_state.final_components_registry.clear()
|
|
for type_name in kit_schemas.keys():
|
|
flat_slots = get_slots_with_inheritance(type_name, kit_schemas)
|
|
|
|
resolved_type_structure = []
|
|
for sequence_id, slot_data in enumerate(flat_slots):
|
|
resolved_type_structure.append({
|
|
"slot_id": sequence_id,
|
|
"name": slot_data["name"],
|
|
"type": slot_data["type"],
|
|
"default": slot_data["default"],
|
|
"flags": slot_data["flags"],
|
|
"summary": slot_data["summary"]
|
|
})
|
|
app_state.final_components_registry[type_name] = resolved_type_structure
|
|
|
|
|
|
def load_schema_kits_and_manifests():
|
|
"""Scans the schema block of the SAX file and maps/loads corresponding manifest definitions."""
|
|
app_state.active_schema_kits.clear()
|
|
if app_state.xml_root_element is None:
|
|
return
|
|
|
|
schema_elem = app_state.xml_root_element.find("schema")
|
|
if schema_elem is None:
|
|
return
|
|
|
|
manifest_paths = []
|
|
for kit in schema_elem.findall("kit"):
|
|
name = kit.get("name")
|
|
checksum = kit.get("checksum")
|
|
if not name:
|
|
continue
|
|
app_state.active_schema_kits[name] = checksum
|
|
|
|
manifest_dir = os.path.join(app_state.SEDONA_HOME, "manifests", name)
|
|
manifest_path = None
|
|
|
|
if checksum:
|
|
target_file = os.path.join(manifest_dir, f"{name}-{checksum}.xml")
|
|
if os.path.exists(target_file):
|
|
manifest_path = target_file
|
|
|
|
if not manifest_path and os.path.exists(manifest_dir):
|
|
files = [f for f in os.listdir(manifest_dir) if f.startswith(f"{name}-") and f.endswith(".xml")]
|
|
if files:
|
|
files.sort()
|
|
manifest_path = os.path.join(manifest_dir, files[-1])
|
|
|
|
if manifest_path:
|
|
manifest_paths.append(manifest_path)
|
|
else:
|
|
messagebox.showerror("Schema Warning", f"Kit {name} is not in Schema selection!")
|
|
sys.exit(1)
|
|
|
|
compile_schema_registry(manifest_paths)
|
|
|
|
|
|
def get_slots_for_type(type_str, linked_slots=()):
|
|
"""
|
|
Returns the slots worth painting for a given type, prefixed with double-digit
|
|
index tags taken from the manifest so hidden rows leave gaps rather than renumber.
|
|
|
|
Visibility, in order of precedence:
|
|
- a linked slot is always shown, so its wire has a row to anchor to
|
|
- '@summary false' hides the slot, even when it is runtime
|
|
- runtime slots (no 'c' flag) are shown; they hold the live signal path
|
|
- config slots that nothing links to are hidden as clutter
|
|
- the 'o' (operator) flag hides a slot outright
|
|
"""
|
|
if type_str not in app_state.final_components_registry:
|
|
return []
|
|
|
|
slots_list = []
|
|
for slot_data in app_state.final_components_registry[type_str]:
|
|
slot_name = slot_data["name"]
|
|
if not slot_name:
|
|
continue
|
|
if "o" in slot_data["flags"]:
|
|
continue
|
|
|
|
is_linked = slot_name in linked_slots
|
|
if not is_linked:
|
|
if not slot_data.get("summary", True):
|
|
continue
|
|
if "c" in slot_data["flags"]:
|
|
continue
|
|
|
|
# Format to double digits (e.g., 00, 01, 02)
|
|
padded_id = f"{slot_data['slot_id']:02d}"
|
|
display_label = f"{padded_id}. {slot_name}"
|
|
|
|
# Keep structural name unchanged for XML lookup inside comp, but pass display label
|
|
slots_list.append((slot_name, display_label))
|
|
|
|
return slots_list
|