Compare commits

..

7 commits

47 changed files with 301 additions and 156 deletions

View file

@ -30,7 +30,7 @@ Rougail est un bibliothèque python3 qui permet de charger des dictionnaires (fi
### Les contraintes ### Les contraintes
- [Les calcules automatiques](fill/README.md) - [Les calculs automatiques](fill/README.md)
- [Les vérifications des valeurs](check/README.md) - [Les vérifications des valeurs](check/README.md)
- [Les conditions](condition/README.md) - [Les conditions](condition/README.md)

View file

@ -111,7 +111,7 @@ $ python3 script.py
## Templatisons un fichier ## Templatisons un fichier
Un template est un fichier dans laquelle on va remplacer les valeurs attendus par le nom des variables. Un [template](../template/README.md) est un fichier dans lequel on va remplacer les valeurs attendues par le nom des variables.
Premièrement déclarons dans un dictionnaire complémentaire notre template dict/00-template.yml : Premièrement déclarons dans un dictionnaire complémentaire notre template dict/00-template.yml :
@ -146,7 +146,6 @@ async def main():
RougailConfig['extra_dictionaries']['example'] = ['extras/'] RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'funcs/functions.py' RougailConfig['functions_file'] = 'funcs/functions.py'
rougail = Rougail() rougail = Rougail()
config = await rougail.get_config()
await rougail.template() await rougail.template()
run(main()) run(main())
@ -162,7 +161,7 @@ The extra value: my_value_extra
## Créons une fonction personnalisé ## Créons une fonction personnalisé
Nous créons un dictionnaire complémentaire pour ajouter un calcul à la variable "my_variable" dans dict/00-fill.yml : Nous créons le dictionnaire complémentaire dict/00-fill.yml pour que la variable "my_variable" soit [calculée](fill/README.md) :
```yml ```yml
version: '0.10' version: '0.10'
@ -190,5 +189,31 @@ The extra value: my_value_extra
La valeur de la variable "my_variable" est bien calculé à partir de la fonction "return_no". La valeur de la variable "my_variable" est bien calculé à partir de la fonction "return_no".
## Template et systemd
#FIXME systemd Rougail peut également généré automatiquement le fichier [tmpfiles.d](https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html) pour installer automatiquement les fichiers de configuration au démarrage de la machine.
Pour générer le fichier tmpfiles.d, ajouter l'argument "systemd" à la methode "template" :
```python
from rougail import Rougail, RougailConfig
from asyncio import run
async def main():
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'funcs/functions.py'
rougail = Rougail()
await rougail.template('systemd')
run(main())
```
Ainsi le fichier supplémentaire "dest/tmpfiles.d/0rougail.conf" sera créé avec le contenu :
```
C /etc/example.conf 0644 root root - /usr/local/lib/etc/example.conf
```

View file

@ -7,9 +7,9 @@
viewBox="0 0 183.75807 175.69795" viewBox="0 0 183.75807 175.69795"
version="1.1" version="1.1"
id="svg1281" id="svg1281"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
sodipodi:docname="schema.svg" sodipodi:docname="schema.svg"
inkscape:export-filename="/home/gnunux/git/risotto/rougail/doc/schema.png" inkscape:export-filename="schema.png"
inkscape:export-xdpi="149.25999" inkscape:export-xdpi="149.25999"
inkscape:export-ydpi="149.25999" inkscape:export-ydpi="149.25999"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
@ -28,14 +28,16 @@
inkscape:document-units="mm" inkscape:document-units="mm"
showgrid="false" showgrid="false"
inkscape:zoom="0.7786055" inkscape:zoom="0.7786055"
inkscape:cx="353.19555" inkscape:cx="-26.329123"
inkscape:cy="355.76425" inkscape:cy="357.0486"
inkscape:window-width="1033" inkscape:window-width="1920"
inkscape:window-height="1063" inkscape:window-height="1011"
inkscape:window-x="26" inkscape:window-x="0"
inkscape:window-y="23" inkscape:window-y="0"
inkscape:window-maximized="0" inkscape:window-maximized="1"
inkscape:current-layer="layer1" /> inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" />
<defs <defs
id="defs1278"> id="defs1278">
<marker <marker

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -151,11 +151,11 @@ services:
servicelist: test servicelist: test
variables: variables:
- variable: - variable:
name: condition - name: condition
type: boolean type: boolean
constraints: constraints:
- condition: - condition:
name: disabled_if_in - name: disabled_if_in
source: condition source: condition
param: param:
- text: false - text: false

View file

@ -58,10 +58,12 @@ def get_annotators(annotators, module_name):
annotators[module_name] = [] annotators[module_name] = []
for pathobj in importlib.resources.files(module_name).iterdir(): for pathobj in importlib.resources.files(module_name).iterdir():
path = str(pathobj) path = str(pathobj)
if not path.endswith('__') and not path.endswith('__.py'): if path.endswith('__') or path.endswith('__.py'):
module = load_modules(path) continue
if 'Annotator' in dir(module): module = load_modules(path)
annotators[module_name].append(module.Annotator) if 'Annotator' not in dir(module):
continue
annotators[module_name].append(module.Annotator)
class SpaceAnnotator: # pylint: disable=R0903 class SpaceAnnotator: # pylint: disable=R0903
@ -69,25 +71,26 @@ class SpaceAnnotator: # pylint: disable=R0903
""" """
def __init__(self, def __init__(self,
objectspace, objectspace,
eosfunc_files,
): ):
global ANNOTATORS global ANNOTATORS
if ANNOTATORS is None: if ANNOTATORS is None:
ANNOTATORS = {} ANNOTATORS = {}
get_annotators(ANNOTATORS, 'rougail.annotator') get_annotators(ANNOTATORS, 'rougail.annotator')
for extra_annotator in objectspace.rougailconfig['extra_annotators']:
get_annotators(ANNOTATORS, extra_annotator)
for extra_annotator in objectspace.rougailconfig['extra_annotators']: for extra_annotator in objectspace.rougailconfig['extra_annotators']:
if extra_annotator not in ANNOTATORS: if extra_annotator in ANNOTATORS:
get_annotators(ANNOTATORS, extra_annotator) continue
get_annotators(ANNOTATORS, extra_annotator)
annotators = ANNOTATORS['rougail.annotator'].copy() annotators = ANNOTATORS['rougail.annotator'].copy()
for extra_annotator in objectspace.rougailconfig['extra_annotators']: for extra_annotator in objectspace.rougailconfig['extra_annotators']:
annotators.extend(ANNOTATORS[extra_annotator]) annotators.extend(ANNOTATORS[extra_annotator])
annotators = sorted(annotators, key=get_level) annotators = sorted(annotators, key=get_level)
functions = [] functions = []
for eosfunc_file in eosfunc_files: functions_files = objectspace.rougailconfig['functions_file']
if isfile(eosfunc_file): if not isinstance(functions_files, list):
functions.extend(dir(load_modules(eosfunc_file))) functions_files = [functions_files]
for functions_file in functions_files:
if isfile(functions_file):
functions.extend(dir(load_modules(functions_file)))
for annotator in annotators: for annotator in annotators:
annotator(objectspace, annotator(objectspace,
functions, functions,

View file

@ -72,7 +72,7 @@ class Annotator(TargetAnnotator, ParamAnnotator):
""" """
remove_indexes = [] remove_indexes = []
for check_idx, check in enumerate(constraints.check): for check_idx, check in enumerate(constraints.check):
if not check.name in self.functions: if not check.name in self.functions and not self.objectspace.just_doc:
msg = _(f'cannot find check function "{check.name}"') msg = _(f'cannot find check function "{check.name}"')
raise DictConsistencyError(msg, 1, check.xmlfiles) raise DictConsistencyError(msg, 1, check.xmlfiles)
if hasattr(check, 'param') and check.param == []: if hasattr(check, 'param') and check.param == []:

View file

@ -38,6 +38,11 @@ from rougail.annotator.param import ParamAnnotator
from rougail.annotator.variable import Walk from rougail.annotator.variable import Walk
class FakeVariable:
def __getattr__(self, name):
return None
class Annotator(TargetAnnotator, ParamAnnotator, Walk): class Annotator(TargetAnnotator, ParamAnnotator, Walk):
"""Annotate condition """Annotate condition
""" """
@ -222,7 +227,7 @@ class Annotator(TargetAnnotator, ParamAnnotator, Walk):
listvars, listvars,
fills, fills,
) )
elif not target.optional: elif not target.optional and self.objectspace.just_doc is False:
msg = f'cannot found target "{target.type}" "{target.name}"' msg = f'cannot found target "{target.type}" "{target.name}"'
raise DictConsistencyError(_(msg), 2, target.xmlfiles) raise DictConsistencyError(_(msg), 2, target.xmlfiles)
remove_targets.append(target_idx) remove_targets.append(target_idx)
@ -323,6 +328,9 @@ class Annotator(TargetAnnotator, ParamAnnotator, Walk):
add_path_prefix=True, add_path_prefix=True,
) )
except DictConsistencyError as err: except DictConsistencyError as err:
if self.objectspace.just_doc:
condition.source = FakeVariable()
continue
if err.errno == 36: if err.errno == 36:
msg = _(f'the source "{condition.source}" in condition cannot be a dynamic ' msg = _(f'the source "{condition.source}" in condition cannot be a dynamic '
f'variable') f'variable')

View file

@ -76,7 +76,7 @@ class Annotator(TargetAnnotator, ParamAnnotator):
""" """
for fill in constraints.fill: for fill in constraints.fill:
# test if the function exists # test if the function exists
if fill.name not in self.functions: if not self.objectspace.just_doc and fill.name not in self.functions:
msg = _(f'cannot find fill function "{fill.name}"') msg = _(f'cannot find fill function "{fill.name}"')
raise DictConsistencyError(msg, 25, fill.xmlfiles) raise DictConsistencyError(msg, 25, fill.xmlfiles)
for target in fill.target: for target in fill.target:

View file

@ -90,7 +90,7 @@ class ParamAnnotator:
path_prefix, path_prefix,
) )
except DictConsistencyError as err: except DictConsistencyError as err:
if err.errno != 42 or not param.optional: if not self.objectspace.just_doc and (err.errno != 42 or not param.optional):
raise err raise err
param_to_delete.append(param_idx) param_to_delete.append(param_idx)
elif param.type == 'function': elif param.type == 'function':
@ -118,7 +118,7 @@ class ParamAnnotator:
raise DictConsistencyError(msg, 53, obj.xmlfiles) raise DictConsistencyError(msg, 53, obj.xmlfiles)
elif param.type == 'index': elif param.type == 'index':
for target in obj.target: for target in obj.target:
if not self.objectspace.paths.is_follower(target.name): if not self.objectspace.just_doc and not self.objectspace.paths.is_follower(target.name):
msg = _(f'"{param.type}" parameter cannot be set with target ' msg = _(f'"{param.type}" parameter cannot be set with target '
f'"{target.name.name}" which is not a follower variable') f'"{target.name.name}" which is not a follower variable')
raise DictConsistencyError(msg, 60, obj.xmlfiles) raise DictConsistencyError(msg, 60, obj.xmlfiles)

View file

@ -91,7 +91,7 @@ class TargetAnnotator(Walk):
if err.errno != 42: if err.errno != 42:
raise err raise err
# for optional variable # for optional variable
if not target.optional: if not target.optional and not self.objectspace.just_doc:
msg = f'cannot found target "{target.type}" "{target.name}"' msg = f'cannot found target "{target.type}" "{target.name}"'
raise DictConsistencyError(_(msg), 12, target.xmlfiles) from err raise DictConsistencyError(_(msg), 12, target.xmlfiles) from err
remove_targets.append(index) remove_targets.append(index)

View file

@ -63,6 +63,7 @@ class RougailConvert:
""" """
def __init__(self, def __init__(self,
rougailconfig: RougailConfig=None, rougailconfig: RougailConfig=None,
just_doc: bool=False,
) -> None: ) -> None:
if rougailconfig is None: if rougailconfig is None:
rougailconfig = RougailConfig rougailconfig = RougailConfig
@ -70,11 +71,9 @@ class RougailConvert:
xmlreflector = Reflector(self.rougailconfig) xmlreflector = Reflector(self.rougailconfig)
self.rougailobjspace = RougailObjSpace(xmlreflector, self.rougailobjspace = RougailObjSpace(xmlreflector,
self.rougailconfig, self.rougailconfig,
just_doc,
) )
self.internal_functions = self.rougailconfig['internal_functions'] self.internal_functions = self.rougailconfig['internal_functions']
self.functions_file = self.rougailconfig['functions_file']
if not isinstance(self.functions_file, list):
self.functions_file = [self.functions_file]
self.dictionaries = False self.dictionaries = False
self.annotator = False self.annotator = False
self.reflector = None self.reflector = None
@ -111,7 +110,7 @@ class RougailConvert:
path_prefix: str, path_prefix: str,
namespace_description: str=None, namespace_description: str=None,
) -> List[str]: ) -> List[str]:
for xmlfile, document in xmlreflector.load_dictionaries_from_folders(xmlfolders): for xmlfile, document in xmlreflector.load_dictionaries_from_folders(xmlfolders, self.rougailobjspace.just_doc):
self.rougailobjspace.xml_parse_document(xmlfile, self.rougailobjspace.xml_parse_document(xmlfile,
document, document,
namespace, namespace,
@ -122,9 +121,7 @@ class RougailConvert:
def annotate(self): def annotate(self):
if self.annotator: if self.annotator:
raise DictConsistencyError(_('Cannot execute annotate multiple time'), 85, None) raise DictConsistencyError(_('Cannot execute annotate multiple time'), 85, None)
SpaceAnnotator(self.rougailobjspace, SpaceAnnotator(self.rougailobjspace)
self.functions_file,
)
self.annotator = True self.annotator = True
def reflexion(self, def reflexion(self,
@ -136,7 +133,10 @@ class RougailConvert:
self.annotate() self.annotate()
if self.reflector: if self.reflector:
raise DictConsistencyError(_('Cannot execute reflexion multiple time'), 86, None) raise DictConsistencyError(_('Cannot execute reflexion multiple time'), 86, None)
functions_file = [func for func in self.functions_file if func not in exclude_imports] functions_file = self.rougailconfig['functions_file']
if not isinstance(functions_file, list):
functions_file = [functions_file]
functions_file = [func for func in functions_file if func not in exclude_imports]
self.reflector = TiramisuReflector(self.rougailobjspace, self.reflector = TiramisuReflector(self.rougailobjspace,
functions_file, functions_file,
self.internal_functions, self.internal_functions,

View file

@ -43,7 +43,7 @@ DEFAULT_LANG = os.environ.get('LANG', '').split(':')
DEFAULT_LANG += ['en_US'] DEFAULT_LANG += ['en_US']
languages = [] languages = []
lc, encoding = locale.getdefaultlocale() lc, encoding = locale.getlocale()
if lc: if lc:
languages = [lc] languages = [lc]

View file

@ -28,7 +28,7 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
""" """
from typing import Optional from typing import Optional, List
from .i18n import _ from .i18n import _
from .reflector import Reflector from .reflector import Reflector
@ -106,7 +106,9 @@ class RougailObjSpace:
def __init__(self, def __init__(self,
xmlreflector: Reflector, xmlreflector: Reflector,
rougailconfig: 'RougailConfig', rougailconfig: 'RougailConfig',
just_doc: bool,
) -> None: ) -> None:
self.just_doc = just_doc
self.space = ObjSpace() self.space = ObjSpace()
self.paths = Path(rougailconfig) self.paths = Path(rougailconfig)
@ -201,6 +203,7 @@ class RougailObjSpace:
namespace_description, namespace_description,
redefine_variables, redefine_variables,
False, False,
True,
) )
def _xml_parse(self, # pylint: disable=R0913 def _xml_parse(self, # pylint: disable=R0913
@ -211,6 +214,7 @@ class RougailObjSpace:
namespace_description, namespace_description,
redefine_variables, redefine_variables,
is_dynamic, is_dynamic,
first_level,
) -> None: ) -> None:
# var to check unique family name in a XML file # var to check unique family name in a XML file
family_names = [] family_names = []
@ -218,6 +222,8 @@ class RougailObjSpace:
if not isinstance(child.tag, str): if not isinstance(child.tag, str):
# doesn't proceed the XML commentaries # doesn't proceed the XML commentaries
continue continue
if first_level and self.just_doc and child.tag == 'services':
continue
if is_dynamic: if is_dynamic:
is_sub_dynamic = True is_sub_dynamic = True
else: else:
@ -246,11 +252,12 @@ class RougailObjSpace:
child, child,
variableobj, variableobj,
) )
self.remove(child, if not self.just_doc:
variableobj, self.remove(child,
redefine_variables, variableobj,
namespace, redefine_variables,
) namespace,
)
if not exists: if not exists:
self.set_path(namespace, self.set_path(namespace,
document, document,
@ -276,6 +283,7 @@ class RougailObjSpace:
namespace_description, namespace_description,
redefine_variables, redefine_variables,
is_sub_dynamic, is_sub_dynamic,
False,
) )
def get_variableobj(self, def get_variableobj(self,
@ -370,7 +378,7 @@ class RougailObjSpace:
# manage object only if already exists, so cancel # manage object only if already exists, so cancel
raise SpaceObjShallNotBeUpdated() raise SpaceObjShallNotBeUpdated()
redefine = convert_boolean(subspace.get('redefine', False)) redefine = convert_boolean(subspace.get('redefine', False))
if redefine is True: if redefine is True and not self.just_doc:
# cannot redefine an inexistant object # cannot redefine an inexistant object
msg = _(f'Redefined object: "{name}" does not exist yet') msg = _(f'Redefined object: "{name}" does not exist yet')
raise DictConsistencyError(msg, 46, [xmlfile]) raise DictConsistencyError(msg, 46, [xmlfile])
@ -432,9 +440,10 @@ class RougailObjSpace:
redefine = convert_boolean(child.attrib.get('redefine', False)) redefine = convert_boolean(child.attrib.get('redefine', False))
if redefine and child.tag == 'variable': if redefine and child.tag == 'variable':
# delete old values # delete old values
has_value = hasattr(variableobj, 'value') if hasattr(variableobj, 'value') and len(child) != 0:
if has_value and len(child) != 0:
del variableobj.value del variableobj.value
if self.just_doc:
variableobj.multi = len(child) > 1
for attr, val in child.attrib.items(): for attr, val in child.attrib.items():
if attr == 'text' and child.tag in self.forced_text_elts_as_name: if attr == 'text' and child.tag in self.forced_text_elts_as_name:
continue continue
@ -469,12 +478,12 @@ class RougailObjSpace:
if child.attrib.get('remove_condition', False): if child.attrib.get('remove_condition', False):
self.remove_condition(variableobj.name) self.remove_condition(variableobj.name)
if child.attrib.get('remove_fill', False): if child.attrib.get('remove_fill', False):
self.remove_fill(variableobj.name) self.remove_fill(variableobj.name, variableobj.xmlfiles)
elif child.tag == 'fill': elif child.tag == 'fill':
for target in child: for target in child:
if target.tag == 'target' and \ if target.tag == 'target' and \
self.paths.get_path(target.text, namespace) in redefine_variables: self.paths.get_path(target.text, namespace) in redefine_variables:
self.remove_fill(target.text) self.remove_fill(target.text, variableobj.xmlfiles)
def remove_check(self, name): def remove_check(self, name):
"""Remove a check with a specified target """Remove a check with a specified target
@ -515,11 +524,15 @@ class RougailObjSpace:
def remove_fill(self, def remove_fill(self,
name: str, name: str,
xmlfiles: List[str],
) -> None: ) -> None:
"""Remove a fill with a specified target """Remove a fill with a specified target
""" """
remove_fills = [] remove_fills = []
constraints = self.get_constraints() constraints = self.get_constraints()
if not hasattr(constraints, 'fill'):
msg = _(f'Cannot remove fill to "{name}", the variable has no fill yet')
raise DictConsistencyError(msg, 89, xmlfiles)
for idx, fill in enumerate(constraints.fill): # pylint: disable=E1101 for idx, fill in enumerate(constraints.fill): # pylint: disable=E1101
for target in fill.target: for target in fill.target:
if target.name == name: if target.name == name:
@ -579,6 +592,8 @@ class RougailObjSpace:
def get_variables(objectspace): def get_variables(objectspace):
"""Iter all variables from the objectspace """Iter all variables from the objectspace
""" """
if not hasattr(objectspace.space, 'variables'):
return
for family in objectspace.space.variables.values(): for family in objectspace.space.variables.values():
yield from _get_variables(family, objectspace.family) yield from _get_variables(family, objectspace.family)

View file

@ -71,6 +71,7 @@ class Reflector:
def load_dictionaries_from_folders(self, def load_dictionaries_from_folders(self,
folders: List[str], folders: List[str],
just_doc: bool,
): ):
"""Loads all the dictionary files located in the folders' list """Loads all the dictionary files located in the folders' list
@ -90,8 +91,8 @@ class Reflector:
if filename in filenames: if filename in filenames:
raise DictConsistencyError(_(f'duplicate dictionary file name {filename}'), 78, [filenames[filename], full_filename]) raise DictConsistencyError(_(f'duplicate dictionary file name {filename}'), 78, [filenames[filename], full_filename])
filenames[filename] = (ext, full_filename) filenames[filename] = (ext, full_filename)
if not filenames: if not filenames and not just_doc:
raise DictConsistencyError(_('there is no dictionary file'), 77, [folder]) raise DictConsistencyError(_('there is no dictionary file'), 77, folders)
file_names = list(filenames.keys()) file_names = list(filenames.keys())
file_names.sort() file_names.sort()
for filename in file_names: for filename in file_names:

View file

@ -62,7 +62,7 @@ log.addHandler(logging.NullHandler())
INFORMATIONS = {'files': ['source', 'mode', 'engine', 'included'], INFORMATIONS = {'files': ['source', 'mode', 'engine', 'included'],
'overrides': ['name', 'source', 'engine'], 'overrides': ['name', 'source', 'engine'],
'service_names': ['doc', 'engine', 'type'], 'service_names': ['doc', 'engine', 'type', 'target', 'undisable'],
} }
DEFAULT = {'files': ['owner', 'group'], DEFAULT = {'files': ['owner', 'group'],
'overrides': [], 'overrides': [],
@ -178,6 +178,10 @@ class RougailLeader:
def index(self, value): def index(self, value):
return self._value.index(value) return self._value.index(value)
def __str__(self):
followers_name = list(self._follower)
return f'RougailLeader({followers_name[0]}) => {followers_name[1:]}'
class RougailExtra: class RougailExtra:
"""Object that implement access to extra variable """Object that implement access to extra variable
@ -212,7 +216,7 @@ class RougailExtra:
return self._suboption.items() return self._suboption.items()
def __str__(self): def __str__(self):
return self._name return f'RougailExtra("{self._name}") => {self._suboption}'
class RougailBaseTemplate: class RougailBaseTemplate:
@ -233,7 +237,12 @@ class RougailBaseTemplate:
templates_dir = [templates_dir] templates_dir = [templates_dir]
for templ_dir in templates_dir: for templ_dir in templates_dir:
self.templates_dir.append(abspath(templ_dir)) self.templates_dir.append(abspath(templ_dir))
self.patches_dir = abspath(rougailconfig['patches_dir']) patches_dir = rougailconfig['patches_dir']
if not isinstance(patches_dir, list):
patches_dir = [patches_dir]
self.patches_dir = []
for p_dir in patches_dir:
self.patches_dir.append(abspath(p_dir))
eos = {} eos = {}
functions_file = rougailconfig['functions_file'] functions_file = rougailconfig['functions_file']
if not isinstance(functions_file, list): if not isinstance(functions_file, list):
@ -259,16 +268,17 @@ class RougailBaseTemplate:
patch_cmd = ['patch', '-d', self.tmp_dir, '-N', '-p1', '-f'] patch_cmd = ['patch', '-d', self.tmp_dir, '-N', '-p1', '-f']
patch_no_debug = ['-s', '-r', '-', '--backup-if-mismatch'] patch_no_debug = ['-s', '-r', '-', '--backup-if-mismatch']
patch_file = join(self.patches_dir, f'{filename}.patch') for patches_dir in self.patches_dir:
if isfile(patch_file): patch_file = join(patches_dir, f'{filename}.patch')
self.log.info(_("Patching template '{filename}' with '{patch_file}'")) if isfile(patch_file):
ret = call(patch_cmd + patch_no_debug + ['-i', patch_file]) self.log.info(_("Patching template '{filename}' with '{patch_file}'"))
if ret: # pragma: no cover ret = call(patch_cmd + patch_no_debug + ['-i', patch_file])
patch_cmd_err = ' '.join(patch_cmd + ['-i', patch_file]) if ret: # pragma: no cover
msg = _(f"Error applying patch: '{patch_file}'\n" patch_cmd_err = ' '.join(patch_cmd + ['-i', patch_file])
f"To reproduce and fix this error {patch_cmd_err}") msg = _(f"Error applying patch: '{patch_file}'\n"
self.log.error(_(msg)) f"To reproduce and fix this error {patch_cmd_err}")
copy(join(templates_dir, filename), self.tmp_dir) self.log.error(_(msg))
copy(join(templates_dir, filename), self.tmp_dir)
def prepare_template(self, def prepare_template(self,
filename: str, filename: str,
@ -362,65 +372,70 @@ class RougailBaseTemplate:
except FileNotFoundError: except FileNotFoundError:
ori_dir = None ori_dir = None
chdir(self.tmp_dir) chdir(self.tmp_dir)
if not self.rougail_variables_dict: try:
await self.load_variables() if not self.rougail_variables_dict:
for templates_dir in self.templates_dir: await self.load_variables()
for template in listdir(templates_dir): for templates_dir in self.templates_dir:
self.prepare_template(template, for template in listdir(templates_dir):
templates_dir, self.prepare_template(template,
) templates_dir,
files_to_delete = [] )
for included in (True, False): files_to_delete = []
for service_obj in await self.config.option('services').list('all'): for included in (True, False):
service_name = await service_obj.option.description() for service_obj in await self.config.option('services').list('all'):
if await service_obj.option('activate').value.get() is False: service_name = await service_obj.option.description()
if included is False and not await service_obj.information.get('undisable', False): if await service_obj.option('activate').value.get() is False:
self.desactive_service(service_name) if included is False and not await service_obj.information.get('undisable', False):
continue self.desactive_service(service_name)
if not included: continue
engine = await service_obj.information.get('engine', None) if not included:
if engine: engine = await service_obj.information.get('engine', None)
self.instance_file({'engine': engine}, if engine:
'service', self.instance_file({'engine': engine},
service_name, 'service',
) service_name,
target_name = await service_obj.information.get('target', None) )
if target_name: target_name = await service_obj.information.get('target', None)
self.target_service(service_name, if target_name:
target_name, self.target_service(service_name,
engine is None, target_name,
) engine is None,
for fills in await service_obj.list('optiondescription'): )
type_ = await fills.option.name() for fills in await service_obj.list('optiondescription'):
for fill_obj in await fills.list('all'): type_ = await fills.option.name()
fill = await fill_obj.value.dict() for fill_obj in await fills.list('all'):
self.get_default(type_, fill, fill_obj) fill = await fill_obj.value.dict()
await self.get_informations(type_, fill, fill_obj) self.get_default(type_, fill, fill_obj)
if 'included' in fill: await self.get_informations(type_, fill, fill_obj)
if (fill['included'] == 'no' and included is True) or \ if 'included' in fill:
(fill['included'] != 'no' and included is False): if (fill['included'] == 'no' and included is True) or \
(fill['included'] != 'no' and included is False):
continue
elif included is True:
continue continue
elif included is True: if fill['activate']:
continue destfilenames = self.instance_file(fill,
if fill['activate']: type_,
destfilenames = self.instance_file(fill, service_name,
type_, )
service_name, if included and fill.get('included', 'no') == 'content':
) files_to_delete.extend(destfilenames)
if included and fill.get('included', 'no') == 'content': elif 'name' in fill:
files_to_delete.extend(destfilenames) self.log.debug(_(f"Instantiation of file '{fill['name']}' disabled"))
elif 'name' in fill: self.post_instance_service(service_name)
self.log.debug(_(f"Instantiation of file '{fill['name']}' disabled")) for filename in files_to_delete:
self.post_instance_service(service_name) unlink(filename)
for filename in files_to_delete: parent = filename
unlink(filename) while True:
parent = filename parent = dirname(parent)
while True: if listdir(parent):
parent = dirname(parent) break
if listdir(parent): rmdir(parent)
break self.post_instance()
rmdir(parent) except Exception as err:
self.post_instance() if ori_dir is not None:
chdir(ori_dir)
raise err
if ori_dir is not None: if ori_dir is not None:
chdir(ori_dir) chdir(ori_dir)
@ -443,12 +458,21 @@ class RougailBaseTemplate:
obj: 'Option', obj: 'Option',
) -> None: ) -> None:
for key in INFORMATIONS.get(type_, []): for key in INFORMATIONS.get(type_, []):
default_key = f'default_{type_}_{key}' if key == 'target':
if default_key in RougailConfig: default_value = None
default_value = RougailConfig[default_key] elif key == 'undisable':
default_value = False
elif key == 'engine' and type_ == 'service_names':
default_value = None
else: else:
default_value = undefined default_key = f'default_{type_}_{key}'
dico[key] = await obj.information.get(key, default_value) if default_key in RougailConfig:
default_value = RougailConfig[default_key]
else:
default_value = undefined
value = await obj.information.get(key, default_value)
if key not in ['target', 'undisable'] or value != default_value:
dico[key] = await obj.information.get(key, default_value)
def desactive_service(self, def desactive_service(self,
*args, *args,
@ -547,7 +571,7 @@ class RougailBaseTemplate:
await suboption.option.name(), await suboption.option.name(),
path, path,
) )
variables[leadership_name] = RougailExtra(await optiondescription.option.name(), {leader_name: leader}, await optiondescription.option.path()) variables[leadership_name] = RougailExtra(await option.option.name(), {leader_name: leader}, await option.option.path())
else: else:
if is_service_namespace == 'root': if is_service_namespace == 'root':
new_is_service_namespace = 'service_name' new_is_service_namespace = 'service_name'
@ -567,16 +591,7 @@ class RougailBaseTemplate:
if is_variable_namespace: if is_variable_namespace:
value = await option.value.get() value = await option.value.get()
self.rougail_variables_dict[await option.option.name()] = value self.rougail_variables_dict[await option.option.name()] = value
if await option.option.issymlinkoption() and await option.option.isfollower(): value = await option.value.get()
value = []
if isinstance(self.config, TiramisuOption):
path = (await option.option.path())[len_root_path:]
else:
path = await option.option.path()
for index in range(await option.value.len()):
value.append(await self.config.option(path, index).value.get())
else:
value = await option.value.get()
variables[await option.option.name()] = value variables[await option.option.name()] = value
if isinstance(is_service_namespace, str) and is_service_namespace + 's' in INFORMATIONS: if isinstance(is_service_namespace, str) and is_service_namespace + 's' in INFORMATIONS:
self.get_default(is_service_namespace + 's', self.get_default(is_service_namespace + 's',

View file

@ -29,7 +29,7 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
""" """
from json import dumps from json import dumps
from os.path import isfile from os.path import isfile, basename
from .i18n import _ from .i18n import _
from .annotator import CONVERT_OPTION from .annotator import CONVERT_OPTION
@ -44,6 +44,12 @@ class BaseElt: # pylint: disable=R0903
path = '.' path = '.'
def sorted_func_name(func_name):
s_func_name = func_name.split('/')
s_func_name.reverse()
return '/'.join(s_func_name)
class TiramisuReflector: class TiramisuReflector:
"""Convert object to tiramisu representation """Convert object to tiramisu representation
""" """
@ -76,7 +82,7 @@ class TiramisuReflector:
" continue", " continue",
" setattr(func, function, getattr(func_, function))", " setattr(func, function, getattr(func_, function))",
]) ])
for funcs_path in funcs_paths: for funcs_path in sorted(funcs_paths, key=sorted_func_name):
if not isfile(funcs_path): if not isfile(funcs_path):
continue continue
self.text['header'].append(f"_load_functions('{funcs_path}')") self.text['header'].append(f"_load_functions('{funcs_path}')")
@ -295,7 +301,6 @@ class Common:
continue continue
if isinstance(value, str): if isinstance(value, str):
value = self.convert_str(value) value = self.convert_str(value)
#pouet self.text['optiondescription'].append(f"{self.option_name}.impl_set_information('{key}', {value})")
self.text['option'].append(f"{self.option_name}.impl_set_information('{key}', {value})") self.text['option'].append(f"{self.option_name}.impl_set_information('{key}', {value})")
def populate_param(self, def populate_param(self,

View file

View file

View file

@ -0,0 +1,13 @@
<?xml version='1.0' encoding='UTF-8'?>
<rougail version="0.10">
<variables>
<family name="general">
<variable name="mode_conteneur_actif" type="string" description="No change">
<value>oui</value>
</variable>
<variable name="mode_conteneur_actif1" type="string" description="No change">
<value>non</value>
</variable>
</family>
</variables>
</rougail>

View file

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<rougail version="0.10">
<variables>
<family name="general">
<variable name="mode_conteneur_actif" redefine="True" remove_fill="True"/>
</family>
</variables>
</rougail>

View file

@ -0,0 +1,16 @@
version: '0.10'
variables:
- family:
- name: general
variables:
- variable:
- name: mode_conteneur_actif
type: string
description: No change
value:
- text: oui
- name: mode_conteneur_actif1
type: string
description: No change
value:
- text: non

View file

@ -0,0 +1,9 @@
version: '0.10'
variables:
- family:
- name: general
variables:
- variable:
- name: mode_conteneur_actif
redefine: true
remove_fill: true

View file

@ -1,11 +1,11 @@
from os import listdir, mkdir, readlink from os import listdir, mkdir, readlink
from os.path import join, isdir, isfile, islink from os.path import join, isdir, isfile, islink
from shutil import rmtree from shutil import rmtree
from pytest import fixture, mark from pytest import fixture, mark, raises
from lxml.etree import parse from lxml.etree import parse
from tiramisu import Config from tiramisu import Config
from rougail import RougailConfig, RougailSystemdTemplate from rougail import RougailConfig, RougailBaseTemplate, RougailSystemdTemplate
template_dirs = 'tests/dictionaries' template_dirs = 'tests/dictionaries'
@ -39,7 +39,7 @@ def find_files(dirname: str,
files.add(join(*root_file)) files.add(join(*root_file))
async def template(test_dir, filename, root): async def template(test_dir, filename, root, engine_name):
test_dir = join(template_dirs, test_dir) test_dir = join(template_dirs, test_dir)
tmp_dir = join(test_dir, '..', 'tmp') tmp_dir = join(test_dir, '..', 'tmp')
@ -71,7 +71,10 @@ async def template(test_dir, filename, root):
RougailConfig['tmpfile_dest_dir'] = '/test/new/file' RougailConfig['tmpfile_dest_dir'] = '/test/new/file'
else: else:
RougailConfig['tmpfile_dest_dir'] = '/usr/local/lib' RougailConfig['tmpfile_dest_dir'] = '/usr/local/lib'
engine = RougailSystemdTemplate(config) if engine_name == 'base':
engine = RougailBaseTemplate(config)
else:
engine = RougailSystemdTemplate(config)
await engine.instance_files() await engine.instance_files()
list_templates = set() list_templates = set()
if isdir(dest_dir): if isdir(dest_dir):
@ -85,6 +88,8 @@ async def template(test_dir, filename, root):
[], [],
list_results, list_results,
) )
if engine_name == 'base' and 'tmpfiles.d/0rougail.conf' in list_results:
list_results.remove('tmpfiles.d/0rougail.conf')
assert list_templates == list_results, f'error with {test_dir}:' assert list_templates == list_results, f'error with {test_dir}:'
for result in list_results: for result in list_results:
template_file = join(dest_dir, result) template_file = join(dest_dir, result)
@ -99,20 +104,40 @@ async def template(test_dir, filename, root):
with open(template_file, 'r') as fh: with open(template_file, 'r') as fh:
generated_file = fh.read() generated_file = fh.read()
assert result_file == generated_file, f'{template_file} content : \n{generated_file}\nexpected: \nresult_file\n' assert result_file == generated_file, f'{template_file} content : \n{generated_file}\nexpected: \nresult_file\n'
rmtree(dest_dir) if isdir(dest_dir):
rmtree(tmp_dir) rmtree(dest_dir)
if isdir(tmp_dir):
rmtree(tmp_dir)
@mark.asyncio @mark.asyncio
async def test_template(test_dir): async def test_template(test_dir):
await template(test_dir, 'base', '1') for engine in ['base', 'systemd']:
not_file = join(template_dirs, test_dir, 'no_' + engine)
if isfile(not_file):
with raises(Exception) as err:
await template(test_dir, 'base', '1', engine)
else:
await template(test_dir, 'base', '1', engine)
@mark.asyncio @mark.asyncio
async def test_template_multi_1(test_dir): async def test_template_multi_1(test_dir):
await template(test_dir, 'multi', '1') for engine in ['base', 'systemd']:
not_file = join(template_dirs, test_dir, 'no_' + engine)
if isfile(not_file):
with raises(Exception) as err:
await template(test_dir, 'multi', '1', engine)
else:
await template(test_dir, 'multi', '1', engine)
@mark.asyncio @mark.asyncio
async def test_template_multi_2(test_dir): async def test_template_multi_2(test_dir):
await template(test_dir, 'multi', '1') for engine in ['base', 'systemd']:
not_file = join(template_dirs, test_dir, 'no_' + engine)
if isfile(not_file):
with raises(Exception) as err:
await template(test_dir, 'multi', '1', engine)
else:
await template(test_dir, 'multi', '1', engine)