import os from dataclasses import dataclass from typing import Optional import yaml @dataclass class PdkAsset: component: str yaml_path: Optional[str] = None gds_path: Optional[str] = None metadata: Optional[dict] = None class PdkRegistry: """Resolve public PDK component names to metadata and public GDS assets.""" def __init__(self, public_root: str): self.public_root = os.path.abspath(public_root) self._asset_cache = {} def resolve(self, component: str) -> PdkAsset: key = (component or "").strip().replace("\\", "/").strip("/") if not key: return PdkAsset(component=component) if key in self._asset_cache: return self._asset_cache[key] yaml_path = self._find_yaml(key) gds_path = self._find_gds(key, yaml_path) metadata = self._load_yaml(yaml_path) if yaml_path else None asset = PdkAsset(component=component, yaml_path=yaml_path, gds_path=gds_path, metadata=metadata) self._asset_cache[key] = asset return asset def _find_yaml(self, key: str) -> Optional[str]: direct = os.path.join(self.public_root, *key.split("/")) candidates = [] if direct.lower().endswith((".yml", ".yaml")): candidates.append(direct) else: name = key.split("/")[-1] candidates.append(os.path.join(direct, f"{name}.yml")) candidates.append(os.path.join(direct, f"{name}.yaml")) for candidate in candidates: if self._inside_root(candidate) and os.path.exists(candidate): return os.path.abspath(candidate) name = key.split("/")[-1] for root, dirs, files in os.walk(self.public_root): if os.path.basename(root) == name: for filename in files: if filename.lower().endswith((".yml", ".yaml")): return os.path.join(root, filename) dirs.clear() return None def _find_gds(self, key: str, yaml_path: Optional[str]) -> Optional[str]: search_dir = os.path.dirname(yaml_path) if yaml_path else os.path.join(self.public_root, *key.split("/")) name = key.split("/")[-1] candidates = [ os.path.join(search_dir, f"{name}_BB.gds"), os.path.join(search_dir, f"{name}.gds"), ] for candidate in candidates: if self._inside_root(candidate) and os.path.exists(candidate): return os.path.abspath(candidate) if os.path.isdir(search_dir): for filename in sorted(os.listdir(search_dir)): if filename.lower().endswith("_bb.gds"): return os.path.join(search_dir, filename) for filename in sorted(os.listdir(search_dir)): if filename.lower().endswith(".gds"): return os.path.join(search_dir, filename) return None def _load_yaml(self, yaml_path: Optional[str]) -> Optional[dict]: if not yaml_path: return None with open(yaml_path, "r", encoding="utf-8") as file: return yaml.safe_load(file) or {} def _inside_root(self, path: str) -> bool: target = os.path.abspath(path) return target == self.public_root or target.startswith(self.public_root + os.sep)