rougail/src/rougail/objspace.py

470 lines
19 KiB
Python
Raw Normal View History

2019-11-23 08:17:35 +01:00
"""
2020-12-23 17:39:21 +01:00
Takes a bunch of Rougail XML dispatched in differents folders
2020-12-23 17:36:50 +01:00
as an input and outputs a Tiramisu's file
2019-11-23 08:17:35 +01:00
Sample usage::
2020-12-23 17:39:21 +01:00
>>> from rougail.objspace import RougailObjSpace
>>> eolobj = RougailObjSpace('/usr/share/rougail/rougail.dtd')
2020-12-23 17:36:50 +01:00
>>> eolobj.create_or_populate_from_xml('rougail', ['/usr/share/rougail/dicos'])
>>> eolobj.space_visitor('/usr/share/rougail/funcs.py')
>>> tiramisu = eolobj.save()
2019-11-23 08:17:35 +01:00
2020-12-23 17:39:21 +01:00
The RougailObjSpace
2019-11-23 08:17:35 +01:00
2020-12-23 17:39:21 +01:00
- loads the XML into an internal RougailObjSpace representation
2019-11-23 08:17:35 +01:00
- visits/annotates the objects
2020-12-23 17:36:50 +01:00
- dumps the object space as Tiramisu string
2019-11-23 08:17:35 +01:00
2020-12-23 17:39:21 +01:00
The visit/annotation stage is a complex step that corresponds to the Rougail
2019-11-23 08:17:35 +01:00
procedures.
"""
2020-12-24 16:02:20 +01:00
from typing import List
2019-11-23 08:17:35 +01:00
from .i18n import _
2020-07-24 14:59:09 +02:00
from .xmlreflector import XMLReflector
2020-12-23 17:36:50 +01:00
from .annotator import SpaceAnnotator
from .tiramisureflector import TiramisuReflector
2019-11-23 08:17:35 +01:00
from .utils import normalize_family
2020-07-20 18:13:53 +02:00
from .error import OperationError, SpaceObjShallNotBeUpdated, DictConsistencyError
2020-07-06 19:47:45 +02:00
from .path import Path
2020-08-12 08:23:38 +02:00
from .config import Config
2019-11-23 08:17:35 +01:00
# RougailObjSpace's elements that shall be forced to the Redefinable type
2020-07-06 19:47:45 +02:00
FORCE_REDEFINABLES = ('family', 'follower', 'service', 'disknod', 'variables')
2020-12-23 17:39:21 +01:00
# RougailObjSpace's elements that shall be forced to the UnRedefinable type
2020-07-07 18:12:16 +02:00
FORCE_UNREDEFINABLES = ('value',)
# RougailObjSpace's elements that shall not be modify
2020-07-16 09:50:01 +02:00
UNREDEFINABLE = ('multi', 'type')
# RougailObjSpace's elements that did not created automaticly
FORCE_ELEMENTS = ('choice', 'property_', 'leadership')
# XML text are convert has name
2020-07-07 18:12:16 +02:00
FORCED_TEXT_ELTS_AS_NAME = ('choice', 'property', 'value', 'target')
2019-11-23 08:17:35 +01:00
# _____________________________________________________________________________
# special types definitions for the Object Space's internal representation
2020-12-23 17:39:21 +01:00
class RootRougailObject:
2020-11-11 16:24:06 +01:00
def __init__(self, xmlfiles):
if not isinstance(xmlfiles, list):
xmlfiles = [xmlfiles]
self.xmlfiles = xmlfiles
2019-11-23 08:17:35 +01:00
2020-12-23 17:39:21 +01:00
class Atom(RootRougailObject):
2020-12-23 17:36:50 +01:00
pass
2020-12-23 17:39:21 +01:00
class Redefinable(RootRougailObject):
2020-12-23 17:36:50 +01:00
pass
2020-12-23 17:39:21 +01:00
class UnRedefinable(RootRougailObject):
2020-12-23 17:36:50 +01:00
pass
class ObjSpace:
2019-11-23 08:17:35 +01:00
"""
Base object space
2019-11-23 08:17:35 +01:00
"""
pass
class RougailObjSpace:
2020-12-24 07:40:14 +01:00
"""Rougail ObjectSpace is an object's reflexion of the XML elements
2019-11-23 08:17:35 +01:00
"""
2020-12-24 07:40:14 +01:00
def __init__(self, dtdfilename):
2019-11-23 08:17:35 +01:00
self.index = 0
self.space = ObjSpace()
2020-07-06 19:47:45 +02:00
self.paths = Path()
2019-11-23 08:17:35 +01:00
self.xmlreflector = XMLReflector()
self.xmlreflector.parse_dtd(dtdfilename)
self.redefine_variables = None
self.forced_text_elts = set()
2020-07-06 19:47:45 +02:00
self.forced_text_elts_as_name = set(FORCED_TEXT_ELTS_AS_NAME)
2019-11-23 08:17:35 +01:00
self.list_conditions = {}
self.booleans_attributs = []
2020-12-24 07:40:14 +01:00
self.make_object_space_classes()
2020-07-06 19:47:45 +02:00
2020-12-24 07:40:14 +01:00
def make_object_space_classes(self):
"""Create Rougail ObjectSpace class types from DDT file
It enables us to create objects like:
2020-07-06 19:47:45 +02:00
File(), Variable(), Ip(), Family(), Constraints()... and so on.
2020-12-24 07:40:14 +01:00
"""
2020-07-06 19:47:45 +02:00
for dtd_elt in self.xmlreflector.dtd.iterelements():
2019-11-23 08:17:35 +01:00
attrs = {}
2020-07-06 19:47:45 +02:00
if dtd_elt.name in FORCE_REDEFINABLES:
2020-12-23 17:36:50 +01:00
clstype = Redefinable
elif not dtd_elt.attributes() and dtd_elt.name not in FORCE_UNREDEFINABLES:
clstype = Atom
2020-07-06 19:47:45 +02:00
else:
2020-12-23 17:36:50 +01:00
clstype = UnRedefinable
2020-07-06 19:47:45 +02:00
forced_text_elt = dtd_elt.type == 'mixed'
for dtd_attr in dtd_elt.iterattributes():
2020-12-23 17:36:50 +01:00
if set(dtd_attr.itervalues()) == {'True', 'False'}:
2020-07-06 19:47:45 +02:00
# it's a boolean
self.booleans_attributs.append(dtd_attr.name)
if dtd_attr.default_value:
# set default value for this attribute
default_value = dtd_attr.default_value
if dtd_attr.name in self.booleans_attributs:
2020-12-23 17:36:50 +01:00
default_value = self.convert_boolean(default_value)
2020-07-06 19:47:45 +02:00
attrs[dtd_attr.name] = default_value
if dtd_attr.name == 'redefine':
# has a redefine attribute, so it's a Redefinable object
2020-12-23 17:36:50 +01:00
clstype = Redefinable
2020-07-06 19:47:45 +02:00
if dtd_attr.name == 'name' and forced_text_elt:
# child.text should be transform has a "name" attribute
self.forced_text_elts.add(dtd_elt.name)
2019-11-23 08:17:35 +01:00
forced_text_elt = False
if forced_text_elt is True:
2020-07-06 19:47:45 +02:00
self.forced_text_elts_as_name.add(dtd_elt.name)
2019-11-23 08:17:35 +01:00
2020-07-06 19:47:45 +02:00
# create ObjectSpace object
setattr(self, dtd_elt.name, type(dtd_elt.name.capitalize(), (clstype,), attrs))
for elt in FORCE_ELEMENTS:
name = elt.capitalize()
if name.endswith('_'):
name = name[:-1]
setattr(self, elt, type(name, (RootRougailObject,), dict()))
self.Leadership = self.leadership
2020-07-06 19:47:45 +02:00
2020-12-24 07:40:14 +01:00
def display_xmlfiles(self,
xmlfiles: list,
) -> str:
if len(xmlfiles) == 1:
return '"' + xmlfiles[0] + '"'
return '"' + '", "'.join(xmlfiles[:-1]) + '"' + ' and ' + '"' + xmlfiles[-1] + '"'
2020-07-06 19:47:45 +02:00
def create_or_populate_from_xml(self,
2020-12-24 16:02:20 +01:00
namespace: str,
xmlfolders: List[str],
) -> List[str]:
2020-12-24 07:40:14 +01:00
"""Parses a bunch of XML files and populates the RougailObjSpace
2020-07-06 19:47:45 +02:00
"""
2020-12-24 16:02:20 +01:00
for xmlfile in self.xmlreflector.load_xml_from_folders(xmlfolders):
document = self.xmlreflector.parse_xmlfile(xmlfile)
2020-07-06 19:47:45 +02:00
self.redefine_variables = []
2020-11-11 16:24:06 +01:00
self.xml_parse_document(xmlfile,
document,
2020-07-06 19:47:45 +02:00
self.space,
namespace,
)
def xml_parse_document(self,
2020-11-11 16:24:06 +01:00
xmlfile,
2020-07-06 19:47:45 +02:00
document,
space,
namespace,
):
2020-12-24 07:40:14 +01:00
"""Parses a Rougail XML file and populates the RougailObjSpace
2020-07-06 19:47:45 +02:00
"""
2020-12-23 17:36:50 +01:00
# var to check unique family name in a XML file
2020-07-06 19:47:45 +02:00
family_names = []
for child in document:
# this index enables us to reorder objects
if not isinstance(child.tag, str):
2020-12-23 17:36:50 +01:00
# doesn't proceed the XML commentaries
2020-07-06 19:47:45 +02:00
continue
if child.tag == 'family':
if child.attrib['name'] in family_names:
2020-12-24 12:41:10 +01:00
raise DictConsistencyError(_(f'Family "{child.attrib["name"]}" is set several times in "{xmlfile}"'), 44)
2020-07-06 19:47:45 +02:00
family_names.append(child.attrib['name'])
if child.tag == 'variables':
2020-12-23 17:36:50 +01:00
# variables has no name, so force namespace name
2020-07-06 19:47:45 +02:00
child.attrib['name'] = namespace
2020-12-23 17:36:50 +01:00
if child.tag == 'value' and child.text is None:
2020-12-24 07:40:14 +01:00
# remove empty value
2020-07-06 19:47:45 +02:00
continue
2020-07-06 20:58:11 +02:00
# variable objects creation
2020-07-06 19:47:45 +02:00
try:
2020-12-23 17:36:50 +01:00
variableobj = self.get_variableobj(xmlfile,
child,
space,
namespace,
)
2020-07-06 19:47:45 +02:00
except SpaceObjShallNotBeUpdated:
continue
2020-12-23 17:36:50 +01:00
self.index += 1
self.set_text(child,
variableobj,
)
self.set_attributes(xmlfile,
child,
variableobj,
)
self.remove(child,
variableobj,
)
self.set_path(space,
child,
namespace,
document,
variableobj,
)
2020-07-06 20:58:11 +02:00
self.add_to_tree_structure(variableobj,
2020-07-06 19:47:45 +02:00
space,
child,
2020-12-23 17:36:50 +01:00
namespace,
2020-07-06 19:47:45 +02:00
)
if list(child) != []:
2020-11-11 16:24:06 +01:00
self.xml_parse_document(xmlfile,
child,
2020-07-06 20:58:11 +02:00
variableobj,
2020-07-06 19:47:45 +02:00
namespace,
)
2020-12-23 17:36:50 +01:00
def get_variableobj(self,
xmlfile: str,
child: list,
space,
namespace,
):
2020-07-06 19:47:45 +02:00
"""
2020-12-24 07:40:14 +01:00
retrieves or creates Rougail Object Subspace objects
2020-07-06 19:47:45 +02:00
"""
2020-12-23 17:36:50 +01:00
obj = getattr(self, child.tag)
if Redefinable in obj.__mro__:
return self.create_or_update_redefinable_object(xmlfile,
child.attrib,
space,
child,
namespace,
)
elif Atom in obj.__mro__:
if child.tag in vars(space):
# Atom instance has to be a singleton here
# we do not re-create it, we reuse it
return getattr(space, child.tag)
return obj(xmlfile)
if child.tag not in vars(space):
setattr(space, child.tag, [])
return obj(xmlfile)
2020-07-06 19:47:45 +02:00
def create_or_update_redefinable_object(self,
2020-11-11 16:24:06 +01:00
xmlfile,
2020-07-06 19:47:45 +02:00
subspace,
space,
child,
namespace,
):
if child.tag in self.forced_text_elts_as_name:
name = child.text
2019-11-23 08:17:35 +01:00
else:
2020-07-06 19:47:45 +02:00
name = subspace['name']
2020-12-23 11:28:43 +01:00
if child.tag == 'family':
name = normalize_family(name)
2020-12-23 17:36:50 +01:00
existed_var = self.get_existed_obj(name,
space,
child,
namespace,
)
2020-11-11 16:24:06 +01:00
if existed_var:
2020-12-23 17:36:50 +01:00
# if redefine is set to object, default value is False
# otherwise it's always a redefinable object
2020-07-06 19:47:45 +02:00
default_redefine = child.tag in FORCE_REDEFINABLES
redefine = self.convert_boolean(subspace.get('redefine', default_redefine))
if redefine is True:
2020-11-11 16:24:06 +01:00
existed_var.xmlfiles.append(xmlfile)
2020-12-23 17:36:50 +01:00
return existed_var
exists = self.convert_boolean(subspace.get('exists', True))
if exists is False:
2020-07-06 19:47:45 +02:00
raise SpaceObjShallNotBeUpdated()
2020-11-11 16:24:06 +01:00
xmlfiles = self.display_xmlfiles(existed_var.xmlfiles)
2020-12-24 12:41:10 +01:00
raise DictConsistencyError(_(f'"{child.tag}" named "{name}" cannot be re-created in "{xmlfile}", already defined in {xmlfiles}'), 45)
2020-12-23 17:36:50 +01:00
# if this object must only be modified if object already exists
2020-07-06 19:47:45 +02:00
exists = self.convert_boolean(subspace.get('exists', False))
2020-12-23 17:36:50 +01:00
if exists is True:
raise SpaceObjShallNotBeUpdated()
redefine = self.convert_boolean(subspace.get('redefine', False))
if redefine is False:
if child.tag not in vars(space):
setattr(space, child.tag, {})
2020-11-11 16:24:06 +01:00
return getattr(self, child.tag)(xmlfile)
2020-12-24 12:41:10 +01:00
raise DictConsistencyError(_(f'Redefined object in "{xmlfile}": "{name}" does not exist yet'), 46)
2020-11-11 16:24:06 +01:00
2020-12-23 17:36:50 +01:00
def get_existed_obj(self,
name: str,
space: str,
child,
namespace: str,
):
2020-12-24 07:40:14 +01:00
if isinstance(space, self.family):
2020-08-12 08:23:38 +02:00
if namespace != Config['variable_namespace']:
2019-11-23 08:17:35 +01:00
name = space.path + '.' + name
2020-12-24 07:40:14 +01:00
if not self.paths.path_is_defined(name):
return
old_family_name = self.paths.get_variable_family_name(name)
if namespace != Config['variable_namespace']:
old_family_name = namespace + '.' + old_family_name
if space.path != old_family_name:
xmlfiles = self.display_xmlfiles(space.xmlfiles)
2020-12-24 12:41:10 +01:00
raise DictConsistencyError(_(f'Variable was previously create in family "{old_family_name}", now it is in "{space.path}" in {xmlfiles}'), 47)
2020-12-24 07:40:14 +01:00
return self.paths.get_variable_obj(name)
2020-11-11 16:24:06 +01:00
children = getattr(space, child.tag, {})
2020-12-23 11:28:43 +01:00
if name in children:
return children[name]
2019-11-23 08:17:35 +01:00
2020-12-24 07:40:14 +01:00
def convert_boolean(self, value):
2020-12-23 17:39:21 +01:00
"""Boolean coercion. The Rougail XML may contain srings like `True` or `False`
2020-07-06 19:47:45 +02:00
"""
if isinstance(value, bool):
return value
if value == 'True':
return True
2020-12-24 07:40:14 +01:00
return False
2020-07-06 19:47:45 +02:00
2020-12-23 17:36:50 +01:00
def set_text(self,
child,
variableobj,
):
if child.text is None:
return
text = child.text.strip()
if not text:
return
if child.tag in self.forced_text_elts_as_name:
variableobj.name = text
2019-11-23 08:17:35 +01:00
else:
2020-12-23 17:36:50 +01:00
variableobj.text = text
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
def set_attributes(self,
xmlfile,
child,
variableobj,
):
redefine = self.convert_boolean(child.attrib.get('redefine', False))
if redefine and child.tag == 'variable':
# delete old values
has_value = hasattr(variableobj, 'value')
if has_value and len(child) != 0:
del variableobj.value
for attr, val in child.attrib.items():
if redefine and attr in UNREDEFINABLE:
xmlfiles = self.display_xmlfiles(variableobj.xmlfiles[:-1])
2020-12-24 12:41:10 +01:00
raise DictConsistencyError(_(f'cannot redefine attribute "{attr}" for variable "{child.attrib["name"]}" in "{xmlfile}", already defined in {xmlfiles}'), 48)
2020-12-23 17:36:50 +01:00
if attr in self.booleans_attributs:
val = self.convert_boolean(val)
if attr == 'name' and getattr(variableobj, 'name', None):
# do not redefine name
continue
setattr(variableobj, attr, val)
2020-11-08 09:51:33 +01:00
2020-12-23 17:36:50 +01:00
def remove(self,
child,
variableobj,
):
2020-12-23 17:39:21 +01:00
"""Rougail object tree manipulations
2020-12-23 17:36:50 +01:00
"""
if child.tag == 'variable':
if child.attrib.get('remove_check', False):
self.remove_check(variableobj.name)
if child.attrib.get('remove_condition', False):
self.remove_condition(variableobj.name)
if child.attrib.get('remove_fill', False):
self.remove_fill(variableobj.name)
if child.tag == 'fill' and child.attrib['target'] in self.redefine_variables:
self.remove_fill(child.attrib['target'])
2020-11-08 09:51:33 +01:00
2020-12-24 07:40:14 +01:00
def remove_check(self, name):
2019-11-23 08:17:35 +01:00
if hasattr(self.space, 'constraints') and hasattr(self.space.constraints, 'check'):
remove_checks = []
2020-12-24 07:40:14 +01:00
for idx, check in enumerate(self.space.constraints.check):
2019-11-23 08:17:35 +01:00
if hasattr(check, 'target') and check.target == name:
remove_checks.append(idx)
remove_checks = list(set(remove_checks))
remove_checks.sort(reverse=True)
for idx in remove_checks:
2020-12-24 07:40:14 +01:00
self.space.constraints.check.pop(idx)
2020-07-06 19:47:45 +02:00
2020-12-24 07:40:14 +01:00
def remove_condition(self, name):
2020-12-22 20:17:32 +01:00
remove_conditions = []
2020-12-24 07:40:14 +01:00
for idx, condition in enumerate(self.space.constraints.condition):
2020-12-22 20:17:32 +01:00
if condition.source == name:
remove_conditions.append(idx)
for idx in remove_conditions:
del self.space.constraints.condition[idx]
2019-11-23 08:17:35 +01:00
2020-12-24 07:40:14 +01:00
def remove_fill(self, name):
2020-12-23 17:36:50 +01:00
if hasattr(self.space, 'constraints') and hasattr(self.space.constraints, 'fill'):
remove_fills= []
2020-12-24 07:40:14 +01:00
for idx, fill in enumerate(self.space.constraints.fill):
2020-12-23 17:36:50 +01:00
if hasattr(fill, 'target') and fill.target == name:
remove_fills.append(idx)
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
remove_fills = list(set(remove_fills))
remove_fills.sort(reverse=True)
for idx in remove_fills:
2020-12-24 07:40:14 +01:00
self.space.constraints.fill.pop(idx)
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
def set_path(self,
space,
child,
namespace,
document,
variableobj,
2020-12-24 07:40:14 +01:00
):
2020-07-06 19:47:45 +02:00
"""Fill self.paths attributes
2019-11-23 08:17:35 +01:00
"""
2020-07-06 19:47:45 +02:00
if child.tag == 'variable':
2020-12-23 11:28:43 +01:00
family_name = document.attrib['name']
family_name = normalize_family(family_name)
2020-07-06 19:47:45 +02:00
self.paths.add_variable(namespace,
child.attrib['name'],
family_name,
document.attrib.get('dynamic') != None,
2020-12-23 11:28:43 +01:00
variableobj,
)
2020-07-06 19:47:45 +02:00
if child.attrib.get('redefine', 'False') == 'True':
2020-08-12 08:23:38 +02:00
if namespace == Config['variable_namespace']:
2020-07-06 19:47:45 +02:00
self.redefine_variables.append(child.attrib['name'])
else:
self.redefine_variables.append(namespace + '.' + family_name + '.' +
child.attrib['name'])
2019-11-23 08:17:35 +01:00
2020-07-06 19:47:45 +02:00
elif child.tag == 'family':
family_name = normalize_family(child.attrib['name'])
2020-08-12 08:23:38 +02:00
if namespace != Config['variable_namespace']:
2020-07-06 19:47:45 +02:00
family_name = namespace + '.' + family_name
self.paths.add_family(namespace,
family_name,
2020-07-06 20:58:11 +02:00
variableobj,
2020-07-06 19:47:45 +02:00
)
2020-07-06 20:58:11 +02:00
variableobj.path = self.paths.get_family_path(family_name, namespace)
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
def add_to_tree_structure(self,
variableobj,
space,
child,
namespace,
2020-12-24 07:40:14 +01:00
):
2020-12-23 17:36:50 +01:00
if not hasattr(variableobj, 'index'):
variableobj.index = self.index
variableobj.namespace = namespace
if isinstance(variableobj, Redefinable):
name = variableobj.name
if child.tag == 'family':
name = normalize_family(name)
getattr(space, child.tag)[name] = variableobj
elif isinstance(variableobj, UnRedefinable):
getattr(space, child.tag).append(variableobj)
else:
setattr(space, child.tag, variableobj)
2020-12-24 07:40:14 +01:00
def space_visitor(self, eosfunc_file):
2020-07-29 08:59:40 +02:00
self.funcs_path = eosfunc_file
2019-11-24 20:25:09 +01:00
SpaceAnnotator(self, eosfunc_file)
2019-11-23 08:17:35 +01:00
2020-12-24 07:40:14 +01:00
def save(self):
tiramisu_objects = TiramisuReflector(self.space,
self.funcs_path,
)
2020-07-29 08:59:40 +02:00
return tiramisu_objects.get_text() + '\n'