135 lines
3.3 KiB
Python
135 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import copy
|
|
import operator
|
|
from functools import reduce
|
|
|
|
from .util import asjson, is_list
|
|
|
|
|
|
class AST(dict):
|
|
_frozen = False
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__()
|
|
self.update(*args, **kwargs)
|
|
self._frozen = True
|
|
|
|
@property
|
|
def frozen(self):
|
|
return self._frozen
|
|
|
|
@property
|
|
def parseinfo(self):
|
|
try:
|
|
return super().__getitem__('parseinfo')
|
|
except KeyError:
|
|
pass
|
|
|
|
def set_parseinfo(self, value):
|
|
super().__setitem__('parseinfo', value)
|
|
|
|
def copy(self):
|
|
return copy.copy(self)
|
|
|
|
def asjson(self):
|
|
return asjson(self)
|
|
|
|
def _set(self, key, value, force_list=False):
|
|
key = self._safekey(key)
|
|
previous = self.get(key)
|
|
|
|
if previous is None and force_list:
|
|
value = [value]
|
|
elif previous is None:
|
|
pass
|
|
elif is_list(previous):
|
|
value = [*previous, value]
|
|
else:
|
|
value = [previous, value]
|
|
|
|
super().__setitem__(key, value)
|
|
|
|
def _setlist(self, key, value):
|
|
return self._set(key, value, force_list=True)
|
|
|
|
def __copy__(self):
|
|
return AST(self)
|
|
|
|
def __getitem__(self, key):
|
|
if key in self:
|
|
return super().__getitem__(key)
|
|
key = self._safekey(key)
|
|
if key in self:
|
|
return super().__getitem__(key)
|
|
return None
|
|
|
|
def __setitem__(self, key, value):
|
|
self._set(key, value)
|
|
|
|
def __delitem__(self, key):
|
|
key = self._safekey(key)
|
|
super().__delitem__(key)
|
|
|
|
def __setattr__(self, name, value):
|
|
if self._frozen and name not in vars(self):
|
|
raise AttributeError(
|
|
f'{type(self).__name__} attributes are fixed. '
|
|
f' Cannot set attribute "{name}".',
|
|
)
|
|
super().__setattr__(name, value)
|
|
|
|
def __getattr__(self, name):
|
|
key = self._safekey(name)
|
|
if key in self:
|
|
return self[key]
|
|
elif name in self:
|
|
return self[name]
|
|
|
|
try:
|
|
return super().__getattribute__(name)
|
|
except AttributeError:
|
|
return None
|
|
|
|
def __hasattribute__(self, name):
|
|
try:
|
|
super().__getattribute__(name)
|
|
except (TypeError, AttributeError):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def __reduce__(self):
|
|
return (AST, (list(self.items()),))
|
|
|
|
def _safekey(self, key):
|
|
while self.__hasattribute__(key):
|
|
key += '_'
|
|
return key
|
|
|
|
def _define(self, keys, list_keys=None):
|
|
for key in (self._safekey(k) for k in keys):
|
|
if key not in self:
|
|
super().__setitem__(key, None)
|
|
|
|
for key in (self._safekey(k) for k in list_keys or []):
|
|
if key not in self:
|
|
super().__setitem__(key, [])
|
|
|
|
def __json__(self, seen=None):
|
|
return {name: asjson(value, seen=seen) for name, value in self.items()}
|
|
|
|
def __repr__(self):
|
|
return repr(self.asjson())
|
|
|
|
def __str__(self):
|
|
return str(self.asjson())
|
|
|
|
def __hash__(self):
|
|
# NOTE: objects are actually mutable during creation
|
|
return reduce(
|
|
operator.xor,
|
|
(hash((name, id(value))) for name, value in self.items()),
|
|
0,
|
|
)
|