"""A small stand-in for the parts of OmegaConf that Snake Quest uses (FredyGames build). The real OmegaConf needs PyYAML and ANTLR, which the in-browser Pyodide runtime of this site does not ship. The build converts every YAML file of the game to a Python literal (`.pylit`, see yaml-to-pylit.py, which uses the real OmegaConf 2.4.0 to read the YAML), and this module loads those: nested dicts and lists with attribute access, `merge`, `create`, `to_container` and `DictConfig`, the only calls the game makes. MIT licence, like the game. """ import ast from pathlib import Path class ConfigAttributeError(AttributeError, KeyError): """A missing key read as an attribute (what OmegaConf raises too).""" def _wrap(value): if isinstance(value, (DictConfig, ListConfig)): return value if isinstance(value, dict): return DictConfig(value) if isinstance(value, (list, tuple)): return ListConfig(value) return value class DictConfig(dict): def __init__(self, content=None): super().__init__() for key, value in (content or {}).items(): dict.__setitem__(self, key, _wrap(value)) def __setitem__(self, key, value): dict.__setitem__(self, key, _wrap(value)) def __getattr__(self, name): try: return self[name] except KeyError: raise ConfigAttributeError(name) from None def __setattr__(self, name, value): self[name] = value def __delattr__(self, name): try: del self[name] except KeyError: raise ConfigAttributeError(name) from None class ListConfig(list): def __init__(self, content=None): super().__init__(_wrap(item) for item in (content or [])) def __setitem__(self, index, value): list.__setitem__(self, index, _wrap(value)) def append(self, value): list.append(self, _wrap(value)) def insert(self, index, value): list.insert(self, index, _wrap(value)) def extend(self, values): list.extend(self, (_wrap(v) for v in values)) def _plain(value): if isinstance(value, dict): return {key: _plain(item) for key, item in value.items()} if isinstance(value, (list, tuple)): return [_plain(item) for item in value] return value def _merge_into(base, other): for key, value in other.items(): if isinstance(value, dict) and isinstance(base.get(key), dict): _merge_into(base[key], value) else: base[key] = _plain(value) class OmegaConf: @staticmethod def load(path): """The YAML file's content, from the `.pylit` next to it.""" return OmegaConf.create(ast.literal_eval(Path(path).with_suffix(".pylit").read_text(encoding="utf-8"))) @staticmethod def create(content=None): return _wrap(_plain(content if content is not None else {})) @staticmethod def to_container(cfg, resolve=False): return _plain(cfg) @staticmethod def merge(*configs): result = _plain(configs[0]) for other in configs[1:]: _merge_into(result, other) return _wrap(result)