diff --git a/src/rougail/__init__.py b/src/rougail/__init__.py index bf8873fb4..959794d92 100644 --- a/src/rougail/__init__.py +++ b/src/rougail/__init__.py @@ -1,4 +1,5 @@ -#from .loader import load +"""Rougail method +""" from .rougail import Rougail from .annotator import modes diff --git a/src/rougail/annotator/fill.py b/src/rougail/annotator/fill.py index d30799ac7..a11f15db7 100644 --- a/src/rougail/annotator/fill.py +++ b/src/rougail/annotator/fill.py @@ -100,6 +100,10 @@ class FillAnnotator: param.text = self.objectspace.paths.get_variable(path) if suffix: param.suffix = suffix + family_path = self.objectspace.paths.get_variable_family_path(path) + param.family = self.objectspace.paths.get_family(family_path, + param.text.namespace, + ) except DictConsistencyError as err: if err.errno != 42 or not param.optional: raise err diff --git a/src/rougail/annotator/group.py b/src/rougail/annotator/group.py index bce242a20..bd6ba4fc0 100644 --- a/src/rougail/annotator/group.py +++ b/src/rougail/annotator/group.py @@ -1,9 +1,6 @@ """Annotate group """ -from typing import List - from ..i18n import _ -from ..config import Config from ..error import DictConsistencyError @@ -22,59 +19,45 @@ class GroupAnnotator: def convert_groups(self): # pylint: disable=C0111 """convert groups """ + # store old leaders family name cache_paths = {} for group in self.objectspace.space.constraints.group: - leader_fullname = group.leader - leader = self.objectspace.paths.get_variable(leader_fullname) - if leader_fullname in cache_paths: - leader_family_path = cache_paths[leader_fullname] + if group.leader in cache_paths: + leader_fam_path = cache_paths[group.leader] else: - leader_family_path = self.objectspace.paths.get_variable_family_path(leader_fullname) - cache_paths[leader_fullname] = leader_family_path - if '.' not in leader_fullname: - leader_fullname = '.'.join([leader_family_path, leader_fullname]) + leader_fam_path = self.objectspace.paths.get_variable_family_path(group.leader) + cache_paths[group.leader] = leader_fam_path follower_names = list(group.follower.keys()) - ori_leader_family = self.objectspace.paths.get_family(leader_family_path, + leader = self.objectspace.paths.get_variable(group.leader) + ori_leader_family = self.objectspace.paths.get_family(leader_fam_path, leader.namespace, ) has_a_leader = False for variable in list(ori_leader_family.variable.values()): - if has_a_leader: - # it's a follower - self.manage_follower(leader_family_path, + if isinstance(variable, self.objectspace.leadership) and \ + variable.variable[0].name == leader.name: + # append follower to an existed leadership + leader_space = variable + has_a_leader = True + elif variable.name == leader.name: + # it's a leader + leader_space = self.manage_leader(variable, + group, + ori_leader_family, + ) + has_a_leader = True + elif has_a_leader: + # it's should be a follower + self.manage_follower(follower_names.pop(0), + leader_fam_path, variable, - leadership_name, - follower_names, + leader_space, ) - if leader_is_hidden: - variable.frozen = True - variable.force_default_on_freeze = True - leader_space.variable.append(variable) + # this variable is not more in ori_leader_family ori_leader_family.variable.pop(variable.name) if follower_names == []: # no more follower break - elif variable.name == leader.name: - # it's a leader - if isinstance(variable, self.objectspace.leadership): - # append follower to an existed leadership - leader_space = variable - # if variable.hidden: - # leader_is_hidden = True - else: - leader_space = self.objectspace.leadership(variable.xmlfiles) - if hasattr(group, 'name'): - leadership_name = group.name - else: - leadership_name = leader.name - leader_is_hidden = self.manage_leader(leader_space, - leader_family_path, - leadership_name, - leader.name, - variable, - group, - ) - has_a_leader = True else: xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) joined = '", "'.join(follower_names) @@ -84,28 +67,27 @@ class GroupAnnotator: del self.objectspace.space.constraints.group def manage_leader(self, - leader_space: 'Leadership', - leader_family_name: str, - leadership_name: str, - leader_name: str, variable: 'Variable', group: 'Group', - ) -> None: + ori_leader_family, + ) -> 'Leadership': """manage leader's variable """ if variable.multi is not True: xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) msg = _(f'the variable "{variable.name}" in a group must be multi in {xmlfiles}') raise DictConsistencyError(msg, 32) + if hasattr(group, 'name'): + leadership_name = group.name + else: + leadership_name = variable.name + leader_space = self.objectspace.leadership(variable.xmlfiles) leader_space.variable = [] leader_space.name = leadership_name leader_space.hidden = variable.hidden if variable.hidden: - leader_is_hidden = True variable.frozen = True variable.force_default_on_freeze = True - else: - leader_is_hidden = False variable.hidden = None if hasattr(group, 'description'): leader_space.doc = group.description @@ -113,38 +95,42 @@ class GroupAnnotator: leader_space.doc = variable.description else: leader_space.doc = leadership_name - namespace = variable.namespace - leadership_path = leader_family_name + '.' + leadership_name - self.objectspace.paths.add_leadership(namespace, + leadership_path = ori_leader_family.path + '.' + leadership_name + self.objectspace.paths.add_leadership(variable.namespace, leadership_path, leader_space, ) - leader_family = self.objectspace.space.variables[namespace].family[leader_family_name.rsplit('.', 1)[-1]] - leader_family.variable[leader_name] = leader_space + leader_family = self.objectspace.paths.get_family(ori_leader_family.path, + ori_leader_family.namespace, + ) + leader_family.variable[variable.name] = leader_space leader_space.variable.append(variable) - self.objectspace.paths.set_leader(namespace, - leader_family_name, + self.objectspace.paths.set_leader(variable.namespace, + ori_leader_family.path, leadership_name, - leader_name, + variable.name, ) - return leader_is_hidden + return leader_space def manage_follower(self, + follower_name: str, leader_family_name: str, variable: 'Variable', - leadership_name: str, - follower_names: List[str], + leader_space: 'Leadership', ) -> None: """manage follower """ - follower_name = follower_names.pop(0) if variable.name != follower_name: xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) - msg = _('when parsing leadership, we espect to find the follower ' + msg = _('when parsing leadership, we expect to find the follower ' f'"{follower_name}" but we found "{variable.name}" in {xmlfiles}') raise DictConsistencyError(msg, 33) self.objectspace.paths.set_leader(variable.namespace, leader_family_name, - leadership_name, + leader_space.name, variable.name, ) + if leader_space.hidden: + variable.frozen = True + variable.force_default_on_freeze = True + leader_space.variable.append(variable) diff --git a/src/rougail/annotator/property.py b/src/rougail/annotator/property.py index ec4767b1f..449578502 100644 --- a/src/rougail/annotator/property.py +++ b/src/rougail/annotator/property.py @@ -36,7 +36,8 @@ class PropertyAnnotator: if hasattr(variable, 'mode') and variable.mode: properties.append(variable.mode) variable.mode = None - if 'force_store_value' in properties and 'force_default_on_freeze' in properties: # pragma: no cover + if 'force_store_value' in properties and \ + 'force_default_on_freeze' in properties: # pragma: no cover # should not appened xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) msg = _('cannot have auto_freeze or auto_store with the hidden ' diff --git a/src/rougail/annotator/service.py b/src/rougail/annotator/service.py index 5494020b0..dbd61d122 100644 --- a/src/rougail/annotator/service.py +++ b/src/rougail/annotator/service.py @@ -10,7 +10,7 @@ from ..error import DictConsistencyError # that shall not be present in the exported (flatened) XML ERASED_ATTRIBUTES = ('redefine', 'exists', 'fallback', 'optional', 'remove_check', 'namespace', 'remove_condition', 'path', 'instance_mode', 'index', 'is_in_leadership', - 'level', 'remove_fill', 'xmlfiles', 'type') + 'level', 'remove_fill', 'xmlfiles', 'type', 'reflector_name', 'reflector_object',) KEY_TYPE = {'variable': 'symlink', @@ -48,16 +48,18 @@ class ServiceAnnotator: self.objectspace.space.services.hidden = True self.objectspace.space.services.name = 'services' self.objectspace.space.services.doc = 'services' + self.objectspace.space.services.path = 'services' families = {} for service_name in self.objectspace.space.services.service.keys(): service = self.objectspace.space.services.service[service_name] new_service = self.objectspace.service(service.xmlfiles) + new_service.path = f'services.{service_name}' for elttype, values in vars(service).items(): if not isinstance(values, (dict, list)) or elttype in ERASED_ATTRIBUTES: setattr(new_service, elttype, values) continue eltname = elttype + 's' - path = '.'.join(['services', service_name, eltname]) + path = '.'.join(['services', normalize_family(service_name), eltname]) family = self._gen_family(eltname, path, service.xmlfiles, @@ -145,7 +147,7 @@ class ServiceAnnotator: c_name = name if idx: c_name += f'_{idx}' - subpath = '{}.{}'.format(path, c_name) + subpath = '{}.{}'.format(path, normalize_family(c_name)) try: self.objectspace.paths.get_family(subpath, 'services') except DictConsistencyError as err: diff --git a/src/rougail/annotator/variable.py b/src/rougail/annotator/variable.py index 4ef19729b..bd23ec6d4 100644 --- a/src/rougail/annotator/variable.py +++ b/src/rougail/annotator/variable.py @@ -1,8 +1,7 @@ """Annotate variable """ -from ..i18n import _ from ..utils import normalize_family -from ..error import DictConsistencyError +from ..config import Config CONVERT_OPTION = {'number': dict(opttype="IntOption", func=int), @@ -120,6 +119,7 @@ class VariableAnnotator: """ for families in self.objectspace.space.variables.values(): families.doc = families.name + families.path = families.name for family in families.family.values(): family.doc = family.name family.name = normalize_family(family.name) diff --git a/src/rougail/error.py b/src/rougail/error.py index fea027061..7e6cbc225 100644 --- a/src/rougail/error.py +++ b/src/rougail/error.py @@ -1,14 +1,18 @@ -# -*- coding: utf-8 -*- +"""Standard error classes +""" class ConfigError(Exception): - pass + """Standard error for templating + """ class FileNotFound(ConfigError): - pass + """Template file is not found + """ class TemplateError(ConfigError): - pass + """Templating generate an error + """ class TemplateDisabled(TemplateError): @@ -29,7 +33,3 @@ class DictConsistencyError(Exception): def __init__(self, msg, errno): super().__init__(msg) self.errno = errno - - -class LoaderError(Exception): - pass diff --git a/src/rougail/objspace.py b/src/rougail/objspace.py index f24f27853..6f1b38e0e 100644 --- a/src/rougail/objspace.py +++ b/src/rougail/objspace.py @@ -306,7 +306,7 @@ class RougailObjSpace: ) -> None: """if an object exists, return it """ - if child.tag == 'family': + if child.tag in ['variable', 'family']: name = normalize_family(name) if isinstance(space, self.family): # pylint: disable=E1101 if namespace != Config['variable_namespace']: @@ -320,6 +320,7 @@ class RougailObjSpace: f'now it is in "{space.path}" in {xmlfiles}') raise DictConsistencyError(msg, 47) return self.paths.get_variable(name) + # it's not a family children = getattr(space, child.tag, {}) if name in children: return children[name] @@ -437,7 +438,7 @@ class RougailObjSpace: if isinstance(variableobj, self.variable): # pylint: disable=E1101 family_name = normalize_family(document.attrib['name']) self.paths.add_variable(namespace, - variableobj.name, + normalize_family(variableobj.name), namespace + '.' + family_name, document.attrib.get('dynamic') is not None, variableobj, @@ -462,7 +463,7 @@ class RougailObjSpace: variableobj.namespace = namespace if isinstance(variableobj, Redefinable): name = variableobj.name - if child.tag == 'family': + if child.tag in ['family', 'variable']: name = normalize_family(name) getattr(space, child.tag)[name] = variableobj elif isinstance(variableobj, UnRedefinable): diff --git a/src/rougail/rougail.py b/src/rougail/rougail.py index 718804350..ebd24e55b 100644 --- a/src/rougail/rougail.py +++ b/src/rougail/rougail.py @@ -28,12 +28,15 @@ from .annotator import SpaceAnnotator class Rougail: + """Rougail object + """ def __init__(self, dtdfilename: str, ) -> None: self.xmlreflector = XMLReflector() self.xmlreflector.parse_dtd(dtdfilename) self.rougailobjspace = RougailObjSpace(self.xmlreflector) + self.funcs_path = None def create_or_populate_from_xml(self, namespace: str, @@ -53,10 +56,14 @@ class Rougail: def space_visitor(self, eosfunc_file: str, ) -> None: + """All XML are loader, now annotate content + """ self.funcs_path = eosfunc_file SpaceAnnotator(self.rougailobjspace, eosfunc_file) def save(self) -> str: + """Return tiramisu object declaration as a string + """ tiramisu_objects = TiramisuReflector(self.rougailobjspace.space, self.funcs_path, ) diff --git a/src/rougail/template.py b/src/rougail/template.py index 67baebcf8..99d4ea4d8 100644 --- a/src/rougail/template.py +++ b/src/rougail/template.py @@ -34,6 +34,8 @@ log.addHandler(logging.NullHandler()) @classmethod def cl_compile(kls, *args, **kwargs): + """Rewrite compile methode to force some settings + """ kwargs['compilerSettings'] = {'directiveStartToken' : '%', 'cheetahVarStartToken' : '%%', 'EOLSlurpToken' : '%', @@ -49,23 +51,16 @@ ChtTemplate.compile = cl_compile class CheetahTemplate(ChtTemplate): - """classe pour personnaliser et faciliter la construction - du template Cheetah + """Construct a cheetah templating object """ def __init__(self, filename: str, context, eosfunc: Dict, - destfilename, - variable, + extra_context: Dict, ): """Initialize Creole CheetahTemplate """ - extra_context = {'normalize_family': normalize_family, - 'rougail_filename': destfilename - } - if variable: - extra_context['rougail_variable'] = variable ChtTemplate.__init__(self, file=filename, searchList=[context, eosfunc, extra_context]) @@ -75,12 +70,12 @@ class CheetahTemplate(ChtTemplate): path=None, normpath=normpath, abspath=abspath - ): + ): # pylint: disable=W0621 # strange... if path is None and isinstance(self, str): path = self - if path: + if path: # pylint: disable=R1705 return normpath(abspath(path)) # original code return normpath(abspath(path.replace("\\", '/'))) elif hasattr(self, '_filePath') and self._filePath: # pragma: no cover @@ -90,6 +85,8 @@ class CheetahTemplate(ChtTemplate): class CreoleLeaderIndex: + """This object is create when access to a specified Index of the variable + """ def __init__(self, value, follower, @@ -136,6 +133,9 @@ class CreoleLeaderIndex: class CreoleLeader: + """Implement access to leader and follower variable + For examples: %%leader, %%leader[0].follower1 + """ def __init__(self, value, ) -> None: @@ -170,6 +170,8 @@ class CreoleLeader: name: str, path: str, ): + """Add a new follower + """ self._follower[name] = [] for index in range(len(self._value)): try: @@ -180,6 +182,9 @@ class CreoleLeader: class CreoleExtra: + """Object that implement access to extra variable + For example %%extra1.family.variable + """ def __init__(self, suboption: Dict) -> None: self.suboption = suboption @@ -216,53 +221,6 @@ class CreoleTemplateEngine: self.eosfunc = eos self.rougail_variables_dict = {} - async def load_eole_variables_rougail(self, - optiondescription, - ): - for option in await optiondescription.list('all'): - if await option.option.isoptiondescription(): - if await option.option.isleadership(): - for idx, suboption in enumerate(await option.list('all')): - if idx == 0: - leader = CreoleLeader(await suboption.value.get()) - self.rougail_variables_dict[await suboption.option.name()] = leader - else: - await leader.add_follower(self.config, - await suboption.option.name(), - await suboption.option.path(), - ) - else: - await self.load_eole_variables_rougail(option) - else: - self.rougail_variables_dict[await option.option.name()] = await option.value.get() - - async def load_eole_variables(self, - optiondescription, - ): - families = {} - for family in await optiondescription.list('all'): - variables = {} - for variable in await family.list('all'): - if await variable.option.isoptiondescription(): - if await variable.option.isleadership(): - for idx, suboption in enumerate(await variable.list('all')): - if idx == 0: - leader = CreoleLeader(await suboption.value.get()) - leader_name = await suboption.option.name() - else: - await leader.add_follower(self.config, - await suboption.option.name(), - await suboption.option.path(), - ) - variables[leader_name] = leader - else: - subfamilies = await self.load_eole_variables(variable) - variables[await variable.option.name()] = subfamilies - else: - variables[await variable.option.name()] = await variable.value.get() - families[await family.option.name()] = CreoleExtra(variables) - return CreoleExtra(families) - def patch_template(self, filename: str, tmp_dir: str, @@ -280,7 +238,9 @@ class CreoleTemplateEngine: ret = call(patch_cmd + patch_no_debug + ['-i', rel_patch_file]) if ret: # pragma: no cover patch_cmd_err = ' '.join(patch_cmd + ['-i', rel_patch_file]) - log.error(_(f"Error applying patch: '{rel_patch_file}'\nTo reproduce and fix this error {patch_cmd_err}")) + msg = _(f"Error applying patch: '{rel_patch_file}'\n" + f"To reproduce and fix this error {patch_cmd_err}") + log.error(_(msg)) copy(join(self.distrib_dir, filename), tmp_dir) def prepare_template(self, @@ -305,18 +265,24 @@ class CreoleTemplateEngine: # full path of the destination file log.info(_(f"Cheetah processing: '{destfilename}'")) try: + extra_context = {'normalize_family': normalize_family, + 'rougail_filename': true_destfilename + } + if variable: + extra_context['rougail_variable'] = variable cheetah_template = CheetahTemplate(source, self.rougail_variables_dict, self.eosfunc, - true_destfilename, - variable, + extra_context, ) data = str(cheetah_template) except CheetahNotFound as err: # pragma: no cover varname = err.args[0][13:-1] - raise TemplateError(_(f"Error: unknown variable used in template {source} to {destfilename} : {varname}")) + msg = f"Error: unknown variable used in template {source} to {destfilename}: {varname}" + raise TemplateError(_(msg)) except Exception as err: # pragma: no cover - raise TemplateError(_(f"Error while instantiating template {source} to {destfilename}: {err}")) + msg = _(f"Error while instantiating template {source} to {destfilename}: {err}") + raise TemplateError(msg) with open(destfilename, 'w') as file_h: file_h.write(data) @@ -366,10 +332,9 @@ class CreoleTemplateEngine: for option in await self.config.option.list(type='all'): namespace = await option.option.name() if namespace == Config['variable_namespace']: - await self.load_eole_variables_rougail(option) + await self.load_variables_namespace(option) else: - families = await self.load_eole_variables(option) - self.rougail_variables_dict[namespace] = families + self.rougail_variables_dict[namespace] = await self.load_variables_extra(option) for template in listdir('.'): self.prepare_template(template, tmp_dir, patch_dir) for service_obj in await self.config.option('services').list('all'): @@ -389,6 +354,57 @@ class CreoleTemplateEngine: log.debug(_("Instantiation of file '{filename}' disabled")) chdir(ori_dir) + async def load_variables_namespace (self, + optiondescription, + ): + """load variables from the "variable namespace + """ + for option in await optiondescription.list('all'): + if await option.option.isoptiondescription(): + if await option.option.isleadership(): + for idx, suboption in enumerate(await option.list('all')): + if idx == 0: + leader = CreoleLeader(await suboption.value.get()) + self.rougail_variables_dict[await suboption.option.name()] = leader + else: + await leader.add_follower(self.config, + await suboption.option.name(), + await suboption.option.path(), + ) + else: + await self.load_variables_namespace(option) + else: + self.rougail_variables_dict[await option.option.name()] = await option.value.get() + + async def load_variables_extra(self, + optiondescription, + ) -> CreoleExtra: + """Load all variables and set it in CreoleExtra objects + """ + families = {} + for family in await optiondescription.list('all'): + variables = {} + for variable in await family.list('all'): + if await variable.option.isoptiondescription(): + if await variable.option.isleadership(): + for idx, suboption in enumerate(await variable.list('all')): + if idx == 0: + leader = CreoleLeader(await suboption.value.get()) + leader_name = await suboption.option.name() + else: + await leader.add_follower(self.config, + await suboption.option.name(), + await suboption.option.path(), + ) + variables[leader_name] = leader + else: + subfamilies = await self.load_variables_extra(variable) + variables[await variable.option.name()] = subfamilies + else: + variables[await variable.option.name()] = await variable.value.get() + families[await family.option.name()] = CreoleExtra(variables) + return CreoleExtra(families) + async def generate(config: Config, eosfunc_file: str, @@ -396,6 +412,8 @@ async def generate(config: Config, tmp_dir: str, dest_dir: str, ) -> None: + """Generate all files + """ engine = CreoleTemplateEngine(config, eosfunc_file, distrib_dir, diff --git a/src/rougail/tiramisu.py b/src/rougail/tiramisu.py index a2b6754c6..133382a56 100644 --- a/src/rougail/tiramisu.py +++ b/src/rougail/tiramisu.py @@ -1,3 +1,5 @@ +"""Redefine Tiramisu object +""" try: from tiramisu3 import DynOptionDescription except ModuleNotFoundError: @@ -6,6 +8,9 @@ from .utils import normalize_family class ConvertDynOptionDescription(DynOptionDescription): + """Suffix could be an integer, we should convert it in str + Suffix could also contain invalid character, so we should "normalize" it + """ def convert_suffix_to_path(self, suffix): if not isinstance(suffix, str): suffix = str(suffix) diff --git a/src/rougail/tiramisureflector.py b/src/rougail/tiramisureflector.py index 77a43661b..5cccd1907 100644 --- a/src/rougail/tiramisureflector.py +++ b/src/rougail/tiramisureflector.py @@ -2,8 +2,6 @@ flattened XML specific """ from .config import Config -from .i18n import _ -from .error import LoaderError from .annotator import ERASED_ATTRIBUTES, CONVERT_OPTION @@ -13,82 +11,76 @@ ATTRIBUTES_ORDER = ('name', 'doc', 'default', 'multi') class Root(): + """Root classes + """ path = '.' class TiramisuReflector: + """Convert object to tiramisu representation + """ def __init__(self, - xmlroot, + space, funcs_path, ): - self.storage = ElementStorage() - self.storage.text = ["from importlib.machinery import SourceFileLoader", - f"func = SourceFileLoader('func', '{funcs_path}').load_module()", - "for key, value in dict(locals()).items():", - " if key != ['SourceFileLoader', 'func']:", - " setattr(func, key, value)", - "try:", - " from tiramisu3 import *", - "except:", - " from tiramisu import *", - "from rougail.tiramisu import ConvertDynOptionDescription", - ] - self.make_tiramisu_objects(xmlroot) - # parse object - self.storage.get(Root()).get() + self.index = 0 + self.text = ["from importlib.machinery import SourceFileLoader", + f"func = SourceFileLoader('func', '{funcs_path}').load_module()", + "for key, value in dict(locals()).items():", + " if key != ['SourceFileLoader', 'func']:", + " setattr(func, key, value)", + "try:", + " from tiramisu3 import *", + "except:", + " from tiramisu import *", + "from rougail.tiramisu import ConvertDynOptionDescription", + ] + self.make_tiramisu_objects(space) def make_tiramisu_objects(self, - xmlroot, + space, ): - family = self.get_root_family() - for xmlelt in self.reorder_family(xmlroot): - self.iter_family(xmlelt, - family, - None, + """make tiramisu objects + """ + baseelt = BaseElt() + self.set_name(baseelt) + basefamily = Family(baseelt, + self, + ) + for elt in self.reorder_family(space): + self.iter_family(basefamily, + elt, ) + # parse object + baseelt.reflector_object.get() - def get_root_family(self): - family = Family(BaseElt(), - self.storage, - False, - '.', - ) - return family - - def reorder_family(self, xmlroot): - # variable_namespace family has to be loaded before any other family - # because `extra` family could use `variable_namespace` variables. - if hasattr(xmlroot, 'variables'): - if Config['variable_namespace'] in xmlroot.variables: - yield xmlroot.variables[Config['variable_namespace']] - for xmlelt, value in xmlroot.variables.items(): - if xmlelt != Config['variable_namespace']: + @staticmethod + def reorder_family(space): + """variable_namespace family has to be loaded before any other family + because `extra` family could use `variable_namespace` variables. + """ + if hasattr(space, 'variables'): + if Config['variable_namespace'] in space.variables: + yield space.variables[Config['variable_namespace']] + for elt, value in space.variables.items(): + if elt != Config['variable_namespace']: yield value - if hasattr(xmlroot, 'services'): - yield xmlroot.services + if hasattr(space, 'services'): + yield space.services def get_attributes(self, space): # pylint: disable=R0201 + """Get attributes + """ for attr in dir(space): if not attr.startswith('_') and attr not in ERASED_ATTRIBUTES: yield attr - def get_children(self, - space, - ): - for tag in self.get_attributes(space): - children = getattr(space, tag) - if children.__class__.__name__ == 'Family': - children = [children] - if isinstance(children, dict): - children = list(children.values()) - if isinstance(children, list): - yield tag, children - def iter_family(self, - child, family, - subpath, + child, ): + """Iter each family + """ tag = child.__class__.__name__ if tag == 'Variable': function = self.populate_variable @@ -97,112 +89,102 @@ class TiramisuReflector: return else: function = self.populate_family - #else: - # raise Exception('unknown tag {}'.format(child.tag)) function(family, child, - subpath, ) def populate_family(self, parent_family, elt, - subpath, ): - path = self.build_path(subpath, - elt, - ) - tag = elt.__class__.__name__ + """Populate family + """ + self.set_name(elt) family = Family(elt, - self.storage, - tag == 'Leadership', - path, + self, ) parent_family.add(family) - for tag, children in self.get_children(elt): + for children in self.get_children(elt): for child in children: - self.iter_family(child, - family, - path, + self.iter_family(family, + child, ) + def get_children(self, + space, + ): + """Get children + """ + for tag in self.get_attributes(space): + children = getattr(space, tag) + if children.__class__.__name__ == 'Family': + children = [children] + if isinstance(children, dict): + children = list(children.values()) + if isinstance(children, list): + yield children + def populate_variable(self, family, elt, - subpath, ): - is_follower = False - is_leader = False + """Populate variable + """ if family.is_leader: - if elt.name != family.elt.name: - is_follower = True - else: - is_leader = True + is_leader = elt.name == family.elt.variable[0].name + is_follower = not is_leader + else: + is_leader = False + is_follower = False + self.set_name(elt) family.add(Variable(elt, - self.storage, + self, is_follower, is_leader, - self.build_path(subpath, - elt, - ) )) - def build_path(self, - subpath, - elt, - ): - if subpath is None: - return elt.name - return subpath + '.' + elt.name + def set_name(self, + elt, + ): + elt.reflector_name = f'option_{self.index}' + self.index += 1 def get_text(self): - return '\n'.join(self.storage.get(Root()).get_text()) + """Get text + """ + return '\n'.join(self.text) class BaseElt: - def __init__(self) -> None: - self.name = 'baseoption' - self.doc = 'baseoption' - - -class ElementStorage: - def __init__(self, - ): - self.paths = {} - self.text = [] - self.index = 0 - - def add(self, path, elt): - self.paths[path] = (elt, self.index) - self.index += 1 - - def get(self, obj): - path = obj.path - return self.paths[path][0] - - def get_name(self, path): - return f'option_{self.paths[path][1]}' + """Base element + """ + name = 'baseoption' + doc = 'baseoption' + path = '.' class Common: + """Common function for variable and family + """ def __init__(self, storage, is_leader, - path, ): self.option_name = None - self.path = path self.attrib = {} self.informations = {} self.storage = storage self.is_leader = is_leader - self.storage.add(self.path, self) + self.elt.reflector_object = self def populate_properties(self, child): + """Populate properties + """ assert child.type == 'calculation' action = f"ParamValue('{child.name}')" - option_name = self.storage.get(child.source).get() - kwargs = f"'condition': ParamOption({option_name}, todict=True), 'expected': ParamValue('{child.expected}')" + option_name = child.source.reflector_object.get() + kwargs = (f"'condition': ParamOption({option_name}, todict=True), " + f"'expected': ParamValue('{child.expected}')") if child.inverse: kwargs += ", 'reverse_condition': ParamValue(True)" prop = 'Calculation(calc_value, Params(' + action + ', kwargs={' + kwargs + '}))' @@ -211,6 +193,8 @@ class Common: self.attrib['properties'] += prop def get_attrib(self): + """Get attributes + """ ret_list = [] for key, value in self.attrib.items(): if value is None: @@ -227,16 +211,16 @@ class Common: return ', '.join(ret_list) def populate_informations(self): + """Populate Tiramisu's informations + """ for key, value in self.informations.items(): if isinstance(value, str): value = '"' + value.replace('"', '\"') + '"' self.storage.text.append(f'{self.option_name}.impl_set_information("{key}", {value})') - def get_text(self, - ): - return self.storage.text - def get_attributes(self, space): # pylint: disable=R0201 + """Get attributes + """ attributes = dir(space) for attr in ATTRIBUTES_ORDER: if attr in attributes: @@ -245,12 +229,14 @@ class Common: if attr not in ATTRIBUTES_ORDER: if not attr.startswith('_') and attr not in ERASED_ATTRIBUTES: value = getattr(space, attr) - if not isinstance(value, (list, dict)) and not value.__class__.__name__ == 'Family': + if not isinstance(value, (list, dict)) and \ + not value.__class__.__name__ == 'Family': yield attr - def get_children(self, - space, - ): + @staticmethod + def get_children(space): + """Get children + """ for attr in dir(space): if not attr.startswith('_') and attr not in ERASED_ATTRIBUTES: if isinstance(getattr(space, attr), list): @@ -258,16 +244,17 @@ class Common: class Variable(Common): + """Manage variable + """ def __init__(self, elt, storage, is_follower, is_leader, - path, ): + self.elt = elt super().__init__(storage, is_leader, - path, ) self.is_follower = is_follower convert_option = CONVERT_OPTION[elt.type] @@ -276,22 +263,25 @@ class Variable(Common): if self.object_type != 'SymLinkOption': self.attrib['properties'] = [] self.attrib['validators'] = [] - self.elt = elt def get(self): + """Get tiramisu's object + """ if self.option_name is None: self.populate_attrib() if self.object_type == 'SymLinkOption': - self.attrib['opt'] = self.storage.get(self.attrib['opt']).get() + self.attrib['opt'] = self.attrib['opt'].reflector_object.get() else: self.parse_children() attrib = self.get_attrib() - self.option_name = self.storage.get_name(self.path) + self.option_name = self.elt.reflector_name self.storage.text.append(f'{self.option_name} = {self.object_type}({attrib})') self.populate_informations() return self.option_name def populate_attrib(self): + """Populate attributes + """ for key in self.get_attributes(self.elt): value = getattr(self.elt, key) if key in FORCE_INFORMATIONS: @@ -306,6 +296,8 @@ class Variable(Common): self.attrib[key] = value def parse_children(self): + """Parse children + """ if 'default' not in self.attrib or self.attrib['multi']: self.attrib['default'] = [] if self.attrib['multi'] == 'submulti' and self.is_follower: @@ -313,7 +305,8 @@ class Variable(Common): choices = [] if 'properties' in self.attrib: if self.attrib['properties']: - self.attrib['properties'] = "'" + "', '".join(sorted(list(self.attrib['properties']))) + "'" + self.attrib['properties'] = "'" + \ + "', '".join(sorted(list(self.attrib['properties']))) + "'" else: self.attrib['properties'] = '' for tag, children in self.get_children(self.elt): @@ -326,10 +319,11 @@ class Variable(Common): else: self.populate_value(child) elif tag == 'check': - self.attrib['validators'].append(self.calculation_value(child, ['ParamSelfOption()'])) + validator = self.calculation_value(child, ['ParamSelfOption()']) + self.attrib['validators'].append(validator) elif tag == 'choice': if child.type == 'calculation': - value = self.storage.get(child.name).get() + value = child.name.reflector_object.get() choices = f"Calculation(func.calc_value, Params((ParamOption({value}))))" else: choices.append(child.name) @@ -347,7 +341,12 @@ class Variable(Common): else: self.attrib['validators'] = '[' + ', '.join(self.attrib['validators']) + ']' - def calculation_value(self, child, args): + def calculation_value(self, + child, + args, + ) -> str: + """Generate calculated value + """ kwargs = [] # has parameters function = child.name @@ -358,7 +357,8 @@ class Variable(Common): args.append(str(value)) else: kwargs.append(f"'{param.name}': " + value) - ret = f"Calculation(func.{function}, Params((" + ', '.join(args) + "), kwargs=" + "{" + ', '.join(kwargs) + "})" + ret = f"Calculation(func.{function}, Params((" + ', '.join(args) + \ + "), kwargs=" + "{" + ', '.join(kwargs) + "})" if hasattr(child, 'warnings_only'): ret += f', warnings_only={child.warnings_only}' return ret + ')' @@ -367,6 +367,8 @@ class Variable(Common): function: str, param, ): + """Populate variable parameters + """ if param.type == 'string': return f'ParamValue("{param.text}")' if param.type == 'number': @@ -378,16 +380,19 @@ class Variable(Common): } if hasattr(param, 'suffix'): value['suffix'] = param.suffix + value['family'] = param.family return self.build_param(value) if param.type == 'information': return f'ParamInformation("{param.text}", None)' if param.type == 'suffix': return 'ParamSuffix()' - raise LoaderError(_('unknown param type {}').format(param.type)) # pragma: no cover + return '' # pragma: no cover def populate_value(self, child, ): + """Populate variable's values + """ value = child.name if self.attrib['multi'] == 'submulti': self.attrib['default_multi'].append(value) @@ -405,61 +410,75 @@ class Variable(Common): def build_param(self, param, ): - option_name = self.storage.get(param['option']).get() + """build variable parameters + """ + option_name = param['option'].reflector_object.get() + ends = f"notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})" if 'suffix' in param: - family = '.'.join(param['option'].path.split('.')[:-1]) - family_option = self.storage.get_name(family) - return f"ParamDynOption({option_name}, '{param['suffix']}', {family_option}, notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})" - return f"ParamOption({option_name}, notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})" + family_name = param['family'].reflector_name + return f"ParamDynOption({option_name}, '{param['suffix']}', {family_name}, {ends}" + return f"ParamOption({option_name}, {ends}" class Family(Common): + """Manage family + """ def __init__(self, elt, storage, - is_leader, - path, ): + self.elt = elt super().__init__(storage, - is_leader, - path, + elt.__class__.__name__ == 'Leadership', ) self.children = [] - self.elt = elt def add(self, child): + """Add a child + """ self.children.append(child) def get(self): + """Get tiramisu's object + """ if not self.option_name: self.populate_attrib() self.parse_children() - self.option_name = self.storage.get_name(self.path) + self.option_name = self.elt.reflector_name object_name = self.get_object_name() - attrib = self.get_attrib() + ', children=[' + ', '.join([child.get() for child in self.children]) + ']' + attrib = self.get_attrib() + \ + ', children=[' + ', '.join([child.get() for child in self.children]) + ']' self.storage.text.append(f'{self.option_name} = {object_name}({attrib})') self.populate_informations() return self.option_name def populate_attrib(self): + """parse a populate attributes + """ for key in self.get_attributes(self.elt): value = getattr(self.elt, key) if key in FORCE_INFORMATIONS: self.informations[key] = value elif key == 'dynamic': - dynamic = self.storage.get(value).get() - self.attrib['suffixes'] = f"Calculation(func.calc_value, Params((ParamOption({dynamic}))))" + dynamic = value.reflector_object.get() + self.attrib['suffixes'] = \ + f"Calculation(func.calc_value, Params((ParamOption({dynamic}))))" else: self.attrib[key] = value def parse_children(self): + """parse current children + """ if 'properties' in self.attrib: - self.attrib['properties'] = "'" + "', '".join(sorted(list(self.attrib['properties']))) + "'" + self.attrib['properties'] = "'" + \ + "', '".join(sorted(list(self.attrib['properties']))) + "'" if hasattr(self.elt, 'property'): for child in self.elt.property: self.populate_properties(child) def get_object_name(self): + """Get family object's name + """ if 'suffixes' in self.attrib: return 'ConvertDynOptionDescription' if not self.is_leader: diff --git a/tests/dictionaries/10leadership_append_hidden/00-base.xml b/tests/dictionaries/10leadership_append_hidden/00-base.xml new file mode 100644 index 000000000..a6691165f --- /dev/null +++ b/tests/dictionaries/10leadership_append_hidden/00-base.xml @@ -0,0 +1,28 @@ + + + + + + non + + + + + + + + + valfill + + + follower1 + + + follower1 + follower2 + + + diff --git a/tests/dictionaries/10leadership_append_hidden/01-base.xml b/tests/dictionaries/10leadership_append_hidden/01-base.xml new file mode 100644 index 000000000..93706d669 --- /dev/null +++ b/tests/dictionaries/10leadership_append_hidden/01-base.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + follower3 + + + diff --git a/tests/dictionaries/10leadership_append_hidden/__init__.py b/tests/dictionaries/10leadership_append_hidden/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/dictionaries/10leadership_append_hidden/makedict/base.json b/tests/dictionaries/10leadership_append_hidden/makedict/base.json new file mode 100644 index 000000000..9e804f6a7 --- /dev/null +++ b/tests/dictionaries/10leadership_append_hidden/makedict/base.json @@ -0,0 +1 @@ +{"rougail.general.mode_conteneur_actif": "non", "rougail.general1.leader.leader": []} diff --git a/tests/dictionaries/10leadership_append_hidden/tiramisu/__init__.py b/tests/dictionaries/10leadership_append_hidden/tiramisu/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/dictionaries/10leadership_append_hidden/tiramisu/base.py b/tests/dictionaries/10leadership_append_hidden/tiramisu/base.py new file mode 100644 index 000000000..590f41a1c --- /dev/null +++ b/tests/dictionaries/10leadership_append_hidden/tiramisu/base.py @@ -0,0 +1,20 @@ +from importlib.machinery import SourceFileLoader +func = SourceFileLoader('func', 'tests/dictionaries/../eosfunc/test.py').load_module() +for key, value in dict(locals()).items(): + if key != ['SourceFileLoader', 'func']: + setattr(func, key, value) +try: + from tiramisu3 import * +except: + from tiramisu import * +from rougail.tiramisu import ConvertDynOptionDescription +option_3 = StrOption(properties=frozenset({'mandatory', 'normal'}), name='mode_conteneur_actif', doc='No change', multi=False, default='non') +option_2 = OptionDescription(name='general', doc='general', properties=frozenset({'normal'}), children=[option_3]) +option_6 = StrOption(properties=frozenset({'force_default_on_freeze', 'frozen'}), name='leader', doc='leader', multi=True) +option_7 = StrOption(properties=frozenset({'force_default_on_freeze', 'frozen', 'normal'}), name='follower1', doc='follower1', multi=True, default=Calculation(func.calc_val, Params((), kwargs={'valeur': ParamValue("valfill")}))) +option_8 = StrOption(properties=frozenset({'force_default_on_freeze', 'frozen', 'normal'}), name='follower2', doc='follower2', multi=True, default=Calculation(func.calc_val, Params((ParamOption(option_7, notraisepropertyerror=False, todict=False)), kwargs={}))) +option_9 = StrOption(properties=frozenset({'force_default_on_freeze', 'frozen', 'normal'}), name='follower3', doc='follower3', multi=True) +option_5 = Leadership(name='leader', doc='leader', properties=frozenset({'hidden', 'normal'}), children=[option_6, option_7, option_8, option_9]) +option_4 = OptionDescription(name='general1', doc='general1', properties=frozenset({'normal'}), children=[option_5]) +option_1 = OptionDescription(name='rougail', doc='rougail', children=[option_2, option_4]) +option_0 = OptionDescription(name='baseoption', doc='baseoption', children=[option_1]) diff --git a/tests/dictionaries/10leadership_append_name/00-base.xml b/tests/dictionaries/10leadership_append_name/00-base.xml new file mode 100644 index 000000000..da62fe5c8 --- /dev/null +++ b/tests/dictionaries/10leadership_append_name/00-base.xml @@ -0,0 +1,28 @@ + + + + + + non + + + + + + + + + + + + valfill + + + follower1 + + + follower1 + follower2 + + + diff --git a/tests/dictionaries/10leadership_append_name/01-base.xml b/tests/dictionaries/10leadership_append_name/01-base.xml new file mode 100644 index 000000000..93706d669 --- /dev/null +++ b/tests/dictionaries/10leadership_append_name/01-base.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + follower3 + + + diff --git a/tests/dictionaries/10leadership_append_name/__init__.py b/tests/dictionaries/10leadership_append_name/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/dictionaries/10leadership_append_name/makedict/base.json b/tests/dictionaries/10leadership_append_name/makedict/base.json new file mode 100644 index 000000000..2be64a766 --- /dev/null +++ b/tests/dictionaries/10leadership_append_name/makedict/base.json @@ -0,0 +1 @@ +{"rougail.general.mode_conteneur_actif": "non", "rougail.general1.leadership.leader": []} diff --git a/tests/dictionaries/10leadership_append_name/tiramisu/__init__.py b/tests/dictionaries/10leadership_append_name/tiramisu/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/dictionaries/10leadership_append_name/tiramisu/base.py b/tests/dictionaries/10leadership_append_name/tiramisu/base.py new file mode 100644 index 000000000..f8e73ada8 --- /dev/null +++ b/tests/dictionaries/10leadership_append_name/tiramisu/base.py @@ -0,0 +1,20 @@ +from importlib.machinery import SourceFileLoader +func = SourceFileLoader('func', 'tests/dictionaries/../eosfunc/test.py').load_module() +for key, value in dict(locals()).items(): + if key != ['SourceFileLoader', 'func']: + setattr(func, key, value) +try: + from tiramisu3 import * +except: + from tiramisu import * +from rougail.tiramisu import ConvertDynOptionDescription +option_3 = StrOption(properties=frozenset({'mandatory', 'normal'}), name='mode_conteneur_actif', doc='No change', multi=False, default='non') +option_2 = OptionDescription(name='general', doc='general', properties=frozenset({'normal'}), children=[option_3]) +option_6 = StrOption(name='leader', doc='leader', multi=True) +option_7 = StrOption(properties=frozenset({'normal'}), name='follower1', doc='follower1', multi=True, default=Calculation(func.calc_val, Params((), kwargs={'valeur': ParamValue("valfill")}))) +option_8 = StrOption(properties=frozenset({'normal'}), name='follower2', doc='follower2', multi=True, default=Calculation(func.calc_val, Params((ParamOption(option_7, notraisepropertyerror=False, todict=False)), kwargs={}))) +option_9 = StrOption(properties=frozenset({'normal'}), name='follower3', doc='follower3', multi=True) +option_5 = Leadership(name='leadership', doc='leadership', properties=frozenset({'normal'}), children=[option_6, option_7, option_8, option_9]) +option_4 = OptionDescription(name='general1', doc='general1', properties=frozenset({'normal'}), children=[option_5]) +option_1 = OptionDescription(name='rougail', doc='rougail', children=[option_2, option_4]) +option_0 = OptionDescription(name='baseoption', doc='baseoption', children=[option_1])