First Commit
Sedona sax editor AI yardimi ile yaziliyor
This commit is contained in:
+196
@@ -0,0 +1,196 @@
|
||||
"""Component palette: the kits installed in Sedona home, and the components inside each.
|
||||
|
||||
Deliberately sourced from Sedona home rather than the open file's <schema>, so the
|
||||
palette shows everything installed and not just the 17 kits this app happens to use.
|
||||
|
||||
Version resolution matters here. A kit directory can hold many manifests — ontrolControl
|
||||
ships 22 — and the manifest filename carries only a checksum, which sorts arbitrarily.
|
||||
The real version number lives in the .kit filename under kits/, as
|
||||
'<kit>-<checksum>-<version>.kit', so that is what is used to decide which manifest is
|
||||
newest.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import app_state
|
||||
|
||||
_versions_cache = {} # kit name -> {checksum: version string}
|
||||
_manifest_cache = {} # kit name -> (version string, {type name: element})
|
||||
_component_cache = {} # 'kit::Type' -> bool
|
||||
|
||||
|
||||
def _manifests_dir():
|
||||
return os.path.join(app_state.SEDONA_HOME, "manifests")
|
||||
|
||||
|
||||
def _kits_dir():
|
||||
return os.path.join(app_state.SEDONA_HOME, "kits")
|
||||
|
||||
|
||||
def list_kits():
|
||||
"""Kit names installed in Sedona home, alphabetically."""
|
||||
directory = _manifests_dir()
|
||||
if not os.path.isdir(directory):
|
||||
return []
|
||||
return sorted(
|
||||
name for name in os.listdir(directory)
|
||||
if os.path.isdir(os.path.join(directory, name))
|
||||
)
|
||||
|
||||
|
||||
def _version_sort_key(version_string):
|
||||
numbers = [int(part) for part in re.findall(r"\d+", version_string)]
|
||||
return numbers or [0]
|
||||
|
||||
|
||||
def kit_versions(kit_name):
|
||||
"""Maps checksum -> version string, read from the .kit filenames under kits/."""
|
||||
if kit_name in _versions_cache:
|
||||
return _versions_cache[kit_name]
|
||||
|
||||
versions = {}
|
||||
kit_dir = os.path.join(_kits_dir(), kit_name)
|
||||
if os.path.isdir(kit_dir):
|
||||
pattern = re.compile(rf"^{re.escape(kit_name)}-([0-9a-fA-F]+)-(.+)\.kit$")
|
||||
for entry in os.listdir(kit_dir):
|
||||
match = pattern.match(entry)
|
||||
if match:
|
||||
versions[match.group(1)] = match.group(2)
|
||||
|
||||
_versions_cache[kit_name] = versions
|
||||
return versions
|
||||
|
||||
|
||||
def resolve_manifest_path(kit_name):
|
||||
"""Picks the manifest to describe a kit, returning (path, version_label).
|
||||
|
||||
Preference order:
|
||||
1. the checksum the open file's <schema> pins, when it declares one
|
||||
2. the highest real version number, via the .kit filenames
|
||||
3. the last manifest by filename, which is what the app has always fallen back to
|
||||
"""
|
||||
kit_dir = os.path.join(_manifests_dir(), kit_name)
|
||||
if not os.path.isdir(kit_dir):
|
||||
return None, ""
|
||||
|
||||
manifests = sorted(
|
||||
entry for entry in os.listdir(kit_dir)
|
||||
if entry.startswith(f"{kit_name}-") and entry.endswith(".xml")
|
||||
)
|
||||
if not manifests:
|
||||
return None, ""
|
||||
|
||||
versions = kit_versions(kit_name)
|
||||
|
||||
pinned = app_state.active_schema_kits.get(kit_name)
|
||||
if pinned:
|
||||
candidate = f"{kit_name}-{pinned}.xml"
|
||||
if candidate in manifests:
|
||||
return os.path.join(kit_dir, candidate), versions.get(pinned, pinned)
|
||||
|
||||
available = []
|
||||
for entry in manifests:
|
||||
checksum = entry[len(kit_name) + 1:-4]
|
||||
if checksum in versions:
|
||||
available.append((_version_sort_key(versions[checksum]), entry, versions[checksum]))
|
||||
|
||||
if available:
|
||||
available.sort()
|
||||
_, entry, version_label = available[-1]
|
||||
return os.path.join(kit_dir, entry), version_label
|
||||
|
||||
entry = manifests[-1]
|
||||
checksum = entry[len(kit_name) + 1:-4]
|
||||
return os.path.join(kit_dir, entry), versions.get(checksum, "")
|
||||
|
||||
|
||||
def _load_kit(kit_name):
|
||||
"""Parses a kit's chosen manifest into (version label, {type name: element})."""
|
||||
if kit_name in _manifest_cache:
|
||||
return _manifest_cache[kit_name]
|
||||
|
||||
path, version_label = resolve_manifest_path(kit_name)
|
||||
types = {}
|
||||
if path:
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
for type_element in root.findall("type"):
|
||||
types[type_element.get("name")] = type_element
|
||||
except Exception as error:
|
||||
print(f"Palette: cannot read manifest {path}: {error}")
|
||||
|
||||
_manifest_cache[kit_name] = (version_label, types)
|
||||
return _manifest_cache[kit_name]
|
||||
|
||||
|
||||
def kit_version_label(kit_name):
|
||||
return _load_kit(kit_name)[0]
|
||||
|
||||
|
||||
def resolve_checksum(kit_name):
|
||||
"""Checksum of the manifest this palette resolved for a kit, or None."""
|
||||
path, _ = resolve_manifest_path(kit_name)
|
||||
if not path:
|
||||
return None
|
||||
filename = os.path.basename(path)
|
||||
if not filename.startswith(f"{kit_name}-") or not filename.endswith(".xml"):
|
||||
return None
|
||||
return filename[len(kit_name) + 1:-4]
|
||||
|
||||
|
||||
def _find_type(qualified_name):
|
||||
if "::" not in qualified_name:
|
||||
return None
|
||||
kit_name, type_name = qualified_name.split("::", 1)
|
||||
return _load_kit(kit_name)[1].get(type_name)
|
||||
|
||||
|
||||
def _is_component(qualified_name, seen=None):
|
||||
"""True when the base chain reaches sys::Component, so the type can live in an app."""
|
||||
if qualified_name == "sys::Component":
|
||||
return True
|
||||
if qualified_name in _component_cache:
|
||||
return _component_cache[qualified_name]
|
||||
|
||||
seen = seen or set()
|
||||
if qualified_name in seen:
|
||||
return False
|
||||
seen.add(qualified_name)
|
||||
|
||||
type_element = _find_type(qualified_name)
|
||||
if type_element is None:
|
||||
result = False
|
||||
else:
|
||||
base = type_element.get("base")
|
||||
result = bool(base) and _is_component(base, seen)
|
||||
|
||||
_component_cache[qualified_name] = result
|
||||
return result
|
||||
|
||||
|
||||
def list_component_types(kit_name):
|
||||
"""Concrete component types in a kit, as (type name, base) pairs.
|
||||
|
||||
Primitives (no base) and abstract types (the 'a' flag) are left out — neither can be
|
||||
placed in an application.
|
||||
"""
|
||||
_, types = _load_kit(kit_name)
|
||||
|
||||
entries = []
|
||||
for type_name, type_element in types.items():
|
||||
if "a" in (type_element.get("flags") or ""):
|
||||
continue
|
||||
if not _is_component(f"{kit_name}::{type_name}"):
|
||||
continue
|
||||
entries.append((type_name, type_element.get("base") or ""))
|
||||
|
||||
return sorted(entries)
|
||||
|
||||
|
||||
def invalidate():
|
||||
"""Clears every cache, for when Sedona home changes in Preferences."""
|
||||
_versions_cache.clear()
|
||||
_manifest_cache.clear()
|
||||
_component_cache.clear()
|
||||
Reference in New Issue
Block a user