import pyparsing as pp
[docs]
class Result:
""" When data are parsed, some (repeated) options can yield
special keys, results in agregating """
[docs]
def result(self):
return self.value
[docs]
class Key:
""" A base class for items, that have to be treated in a special way """
NONE = lambda x:x
""" The regular items has no special key """
[docs]
def __init__(self, key):
self.key = key
[docs]
def get(self, too):
if self.key in too:
out = too[self.key]
if out.__class__ is not self.ResultClass:
raise pp.ParseException(f"Conflicting values for item {self.key}")
return out
out = self.ResultClass()
too[self.key] = out
too.process.append(self.key)
return out
[docs]
class IgnoredKey(Key):
""" This key is totaly and silently ignored. Use it
for the keys, that should be written, but not read.
"""
[docs]
def add(self, too, val):
return
[docs]
class ValidateKey(Key):
""" This key is totaly and silently ignored. Use it
for the keys, that should be written, but not read.
"""
[docs]
def add(self, too, val):
too.checks.append(val)
[docs]
class SubKey(Key):
""" A base class for items that have subkeys """
[docs]
def __init__(self, key, sub):
self.key = key
self.sub = self.convert(sub)
[docs]
def convert(self, sub):
return int(sub)
[docs]
class DictKey(SubKey):
[docs]
class ResultClass(Result):
def __init__(self):
self.value = {}
[docs]
def add(self, too, value):
out=self.get(too)
if self.sub in out.value:
raise pp.ParseException(f"Duplicate key {self.sub} in {self.key}")
out.value[self.sub] = value
[docs]
class DefDictKey(DictKey):
[docs]
def convert(self, sub):
return sub if sub == 'def' else int(sub)
[docs]
class ArrayKey(SubKey):
[docs]
class ResultClass(Result):
NOT_SET = object()
def __init__(self):
self.value = []
[docs]
def result(self):
return self.value
[docs]
def add(self, too, val):
out = self.get(too)
ln = len(out.value)
i = self.sub - 1
if ln < i:
out.value+=[out.NOT_SET] * (i - ln + 1)
if ln == i:
out.value.append(val)
else:
if out.value[i] is not out.NOT_SET:
raise pp.ParseException(f"Duplicate key {self.sub} in {self.key}")
out.value[i]=val
[docs]
class RepeatedKey(Key):
[docs]
class ResultClass(Result):
def __init__(self):
self.value = []
[docs]
def add(self, too, val):
self.get(too).value.append(val)
[docs]
class Values(dict):
""" Result of dict_from_parsed: dictionary with list of checks on the parsed values."""
[docs]
def __init__(self):
super().__init__()
self.checks = []
self.process = []
[docs]
def to_dict(self):
return { i: j.to_dict() if isinstance(j, Values) else j
for i,j in self.items() }
[docs]
def dict_from_parsed(values):
""" Create a dictionary from the arguments.
From duplicate arguments create numpy arrays.
Moreover, if there is key of type (a,b), it will be transformed to subdictionary.
Such a keys do not allow duplicates.
>>> dict_from_parsed( [ ('x', 'y'), ((DictKey('a', 1)), 1 ), ((DictKey('a', 3)), 2) ] )
{'x': 'y', 'a': {1: 1, 3: 2}}
>>> dict_from_parsed( [ ('x', 1), ('x', '2') ] ) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
pyparsing.exceptions.ParseException: There are non-unique keys: x
>>> dict_from_parsed( [ (RepeatedKey('x'), 1), (RepeatedKey('x'), 2) ] )
{'x': [1, 2]}
"""
out = Values()
duplicates = set()
errors = []
def add(key, value):
if isinstance(key, Key):
key.add(out, value)
elif key in out:
duplicates.add(k)
else:
out[key] = value
for k, v in values:
try:
add(k, v)
except Exception as e:
errors.append(e)
for key in out.process:
out[key] = out[key].result()
for i in out.checks:
try:
i(out)
except Exception as e:
errors.append(e)
if duplicates:
duplicates = ", ".join((i.upper() for i in duplicates))
errors.append(pp.ParseException(f"There are duplicate items named {duplicates}"))
if errors:
if len(errors) == 1:
raise errors[0]
errors = '\n'.join((str(e) for e in errors))
raise pp.ParseException(f"There are errors in data: {errors}")
return out