rougail/src/rougail/template.py

414 lines
15 KiB
Python
Raw Permalink Normal View History

2019-12-02 10:31:55 +01:00
# -*- coding: utf-8 -*-
"""
Gestion du mini-langage de template
On travaille sur les fichiers cibles
"""
import imp
from shutil import copy
import logging
2019-12-22 14:46:16 +01:00
from typing import Dict, Any
2019-12-02 10:31:55 +01:00
from subprocess import call
2020-07-08 16:32:10 +02:00
from os import listdir, makedirs
from os.path import dirname, join, isfile
2020-02-16 21:27:42 +01:00
2019-12-02 10:31:55 +01:00
from Cheetah.Template import Template as ChtTemplate
from Cheetah.NameMapper import NotFound as CheetahNotFound
from tiramisu import Config
2019-12-20 11:02:26 +01:00
from tiramisu.error import PropertiesOptionError
2019-12-02 10:31:55 +01:00
2020-07-20 18:13:53 +02:00
from .config import patch_dir, variable_namespace
2020-07-08 16:32:10 +02:00
from .error import FileNotFound, TemplateError
2019-12-02 10:31:55 +01:00
from .i18n import _
from .utils import normalize_family
2019-12-02 10:31:55 +01:00
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
2020-07-08 16:32:10 +02:00
2019-12-20 11:02:26 +01:00
class IsDefined:
2019-12-02 10:31:55 +01:00
"""
filtre permettant de ne pas lever d'exception au cas où
la variable Creole n'est pas définie
"""
def __init__(self, context):
self.context = context
def __call__(self, varname):
if '.' in varname:
splitted_var = varname.split('.')
if len(splitted_var) != 2:
2020-04-18 09:00:03 +02:00
msg = _("Group variables must be of type leader.follower")
2019-12-02 10:31:55 +01:00
raise KeyError(msg)
2020-04-18 09:00:03 +02:00
leader, follower = splitted_var
if leader in self.context:
return follower in self.context[leader].follower.keys()
2019-12-02 10:31:55 +01:00
return False
else:
return varname in self.context
@classmethod
def cl_compile(kls, *args, **kwargs):
kwargs['compilerSettings'] = {'directiveStartToken' : '%',
2019-12-20 11:02:26 +01:00
'cheetahVarStartToken' : '%%',
'EOLSlurpToken' : '%',
'PSPStartToken' : 'µ' * 10,
'PSPEndToken' : 'µ' * 10,
'commentStartToken' : 'µ' * 10,
'commentEndToken' : 'µ' * 10,
'multiLineCommentStartToken' : 'µ' * 10,
'multiLineCommentEndToken' : 'µ' * 10}
2019-12-02 10:31:55 +01:00
return kls.old_compile(*args, **kwargs)
ChtTemplate.old_compile = ChtTemplate.compile
ChtTemplate.compile = cl_compile
class CheetahTemplate(ChtTemplate):
"""classe pour personnaliser et faciliter la construction
du template Cheetah
"""
def __init__(self,
filename: str,
context,
eosfunc: Dict,
2019-12-22 14:46:16 +01:00
destfilename,
variable):
2019-12-02 10:31:55 +01:00
"""Initialize Creole CheetahTemplate
"""
2019-12-22 14:46:16 +01:00
extra_context = {'is_defined' : IsDefined(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])
2019-12-02 10:31:55 +01:00
2019-12-20 11:02:26 +01:00
class CreoleLeader:
2020-04-18 09:00:03 +02:00
def __init__(self, value, follower=None, index=None):
2019-12-02 10:31:55 +01:00
"""
On rend la variable itérable pour pouvoir faire:
for ip in iplist:
2020-02-18 22:12:55 +01:00
print(ip.network)
print(ip.netmask)
print(ip)
2019-12-02 10:31:55 +01:00
index is used for CreoleLint
"""
self._value = value
2020-04-18 09:00:03 +02:00
if follower is not None:
self.follower = follower
2019-12-02 10:31:55 +01:00
else:
2020-04-18 09:00:03 +02:00
self.follower = {}
2019-12-02 10:31:55 +01:00
self._index = index
def __getattr__(self, name):
2020-04-18 09:00:03 +02:00
"""Get follower variable or attribute of leader value.
2019-12-02 10:31:55 +01:00
2020-04-18 09:00:03 +02:00
If the attribute is a name of a follower variable, return its value.
Otherwise, returns the requested attribute of leader value.
2019-12-02 10:31:55 +01:00
"""
2020-04-18 09:00:03 +02:00
if name in self.follower:
value = self.follower[name]
2019-12-20 11:02:26 +01:00
if isinstance(value, PropertiesOptionError):
raise AttributeError()
2019-12-02 10:31:55 +01:00
return value
else:
return getattr(self._value, name)
def __getitem__(self, index):
2020-04-18 09:00:03 +02:00
"""Get a leader.follower at requested index.
2019-12-02 10:31:55 +01:00
"""
ret = {}
2020-04-18 09:00:03 +02:00
for key, values in self.follower.items():
2019-12-02 10:31:55 +01:00
ret[key] = values[index]
2019-12-20 11:02:26 +01:00
return CreoleLeader(self._value[index], ret, index)
2019-12-02 10:31:55 +01:00
def __iter__(self):
2020-04-18 09:00:03 +02:00
"""Iterate over leader.follower.
2019-12-02 10:31:55 +01:00
2020-04-18 09:00:03 +02:00
Return synchronised value of leader.follower.
2019-12-02 10:31:55 +01:00
"""
for i in range(len(self._value)):
ret = {}
2020-04-18 09:00:03 +02:00
for key, values in self.follower.items():
2019-12-02 10:31:55 +01:00
ret[key] = values[i]
2019-12-20 11:02:26 +01:00
yield CreoleLeader(self._value[i], ret, i)
2019-12-02 10:31:55 +01:00
def __len__(self):
2020-04-18 09:00:03 +02:00
"""Delegate to leader value
2019-12-02 10:31:55 +01:00
"""
return len(self._value)
def __repr__(self):
2019-12-20 11:02:26 +01:00
"""Show CreoleLeader as dictionary.
2019-12-02 10:31:55 +01:00
2020-04-18 09:00:03 +02:00
The leader value is stored under 'value' key.
The followers are stored under 'follower' key.
2019-12-02 10:31:55 +01:00
"""
2020-04-18 09:00:03 +02:00
return repr({'value': self._value, 'follower': self.follower})
2019-12-02 10:31:55 +01:00
def __eq__(self, value):
return value == self._value
def __ne__(self, value):
return value != self._value
def __lt__(self, value):
return self._value < value
def __le__(self, value):
return self._value <= value
def __gt__(self, value):
return self._value > value
def __ge__(self, value):
return self._value >= value
def __str__(self):
2020-04-18 09:00:03 +02:00
"""Delegate to leader value
2019-12-02 10:31:55 +01:00
"""
return str(self._value)
def __add__(self, val):
return self._value.__add__(val)
def __radd__(self, val):
return val + self._value
def __contains__(self, item):
return item in self._value
2020-04-18 09:00:03 +02:00
async def add_follower(self, config, name, path):
2019-12-02 10:31:55 +01:00
if isinstance(self._value, list):
2019-12-20 11:02:26 +01:00
values = []
for idx in range(len(self._value)):
try:
2020-01-13 19:44:24 +01:00
values.append(await config.option(path, idx).value.get())
2019-12-20 11:02:26 +01:00
except PropertiesOptionError as err:
values.append(err)
else:
raise Exception('hu?')
2020-04-18 09:00:03 +02:00
self.follower[name] = values
2019-12-02 10:31:55 +01:00
2019-12-22 15:35:50 +01:00
class CreoleExtra:
def __init__(self,
suboption: Dict) -> None:
self.suboption = suboption
def __getattr__(self,
key: str) -> Any:
return self.suboption[key]
def __repr__(self):
return self.suboption.__str__()
def __iter__(self):
return iter(self.suboption.values())
2019-12-22 15:35:50 +01:00
2019-12-02 10:31:55 +01:00
class CreoleTemplateEngine:
"""Engine to process Creole cheetah template
"""
def __init__(self,
config: Config,
2019-12-02 14:23:02 +01:00
eosfunc_file: str,
distrib_dir: str,
tmp_dir: str,
dest_dir: str,
) -> None:
2019-12-02 10:31:55 +01:00
self.config = config
2019-12-02 14:23:02 +01:00
self.dest_dir = dest_dir
self.tmp_dir = tmp_dir
self.distrib_dir = distrib_dir
2019-12-02 10:31:55 +01:00
eos = {}
2020-02-16 21:27:42 +01:00
if eosfunc_file is not None:
eosfunc = imp.load_source('eosfunc', eosfunc_file)
for func in dir(eosfunc):
if not func.startswith('_'):
eos[func] = getattr(eosfunc, func)
2019-12-02 10:31:55 +01:00
self.eosfunc = eos
2020-07-06 20:58:11 +02:00
self.rougail_variables_dict = {}
2020-01-13 19:44:24 +01:00
2020-07-06 20:58:11 +02:00
async def load_eole_variables_rougail(self,
optiondescription):
2020-01-13 19:44:24 +01:00
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')):
2019-12-20 11:02:26 +01:00
if idx == 0:
2020-01-13 19:44:24 +01:00
leader = CreoleLeader(await suboption.value.get())
2020-07-06 20:58:11 +02:00
self.rougail_variables_dict[await suboption.option.name()] = leader
2019-12-20 11:02:26 +01:00
else:
2020-04-18 09:00:03 +02:00
await leader.add_follower(self.config,
2020-01-13 19:44:24 +01:00
await suboption.option.name(),
await suboption.option.path())
2019-12-02 10:31:55 +01:00
else:
2020-07-06 20:58:11 +02:00
await self.load_eole_variables_rougail(option)
2019-12-02 10:31:55 +01:00
else:
2020-07-06 20:58:11 +02:00
self.rougail_variables_dict[await option.option.name()] = await option.value.get()
2019-12-02 10:31:55 +01:00
2020-01-13 19:44:24 +01:00
async def load_eole_variables(self,
namespace,
optiondescription):
2019-12-22 15:35:50 +01:00
families = {}
2020-01-13 19:44:24 +01:00
for family in await optiondescription.list('all'):
2019-12-22 15:35:50 +01:00
variables = {}
2020-01-13 19:44:24 +01:00
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(await variable.option.name(),
variable,
)
variables[await variable.option.name()] = subfamilies
2019-12-22 15:35:50 +01:00
else:
2020-01-13 19:44:24 +01:00
variables[await variable.option.name()] = await variable.value.get()
families[await family.option.name()] = CreoleExtra(variables)
return CreoleExtra(families)
2019-12-22 15:35:50 +01:00
2019-12-02 10:31:55 +01:00
def patch_template(self,
filename: str):
"""Apply patch to a template
"""
2019-12-02 14:23:02 +01:00
patch_cmd = ['patch', '-d', self.tmp_dir, '-N', '-p1']
2019-12-02 10:31:55 +01:00
patch_no_debug = ['-s', '-r', '-', '--backup-if-mismatch']
# patches variante + locaux
for directory in [join(patch_dir, 'variante'), patch_dir]:
patch_file = join(directory, f'{filename}.patch')
if isfile(patch_file):
log.info(_("Patching template '{filename}' with '{patch_file}'"))
ret = call(patch_cmd + patch_no_debug + ['-i', patch_file])
if ret:
patch_cmd_err = ' '.join(patch_cmd + ['-i', patch_file])
log.error(_(f"Error applying patch: '{patch_file}'\nTo reproduce and fix this error {patch_cmd_err}"))
2019-12-02 14:23:02 +01:00
copy(filename, self.tmp_dir)
2019-12-02 10:31:55 +01:00
def prepare_template(self,
filename: str):
"""Prepare template source file
"""
2019-12-02 14:23:02 +01:00
log.info(_("Copy template: '{filename}' -> '{self.tmp_dir}'"))
copy(filename, self.tmp_dir)
2019-12-02 10:31:55 +01:00
self.patch_template(filename)
def process(self,
source: str,
true_destfilename: str,
2019-12-02 14:23:02 +01:00
destfilename: str,
2019-12-02 10:31:55 +01:00
filevar: Dict,
2019-12-22 14:46:16 +01:00
variable: Any):
2019-12-02 10:31:55 +01:00
"""Process a cheetah template
"""
# full path of the destination file
log.info(_(f"Cheetah processing: '{destfilename}'"))
try:
cheetah_template = CheetahTemplate(source,
2020-07-06 20:58:11 +02:00
self.rougail_variables_dict,
2019-12-02 10:31:55 +01:00
self.eosfunc,
true_destfilename,
2020-07-06 20:58:11 +02:00
variable,
)
2019-12-02 10:31:55 +01:00
data = str(cheetah_template)
except CheetahNotFound as err:
varname = err.args[0][13:-1]
raise TemplateError(_(f"Error: unknown variable used in template {destfilename} : {varname}"))
except Exception as err:
raise TemplateError(_(f"Error while instantiating template {destfilename}: {err}"))
with open(destfilename, 'w') as file_h:
file_h.write(data)
def instance_file(self,
filevar: Dict,
service_name: str) -> None:
2020-02-16 21:27:42 +01:00
"""Run templatisation on one file
2019-12-02 10:31:55 +01:00
"""
log.info(_("Instantiating file '{filename}'"))
2019-12-22 14:46:16 +01:00
if 'variable' in filevar:
variable = filevar['variable']
else:
variable = None
filenames = filevar['name']
if not isinstance(filenames, list):
filenames = [filenames]
if variable:
variable = [variable]
2019-12-22 14:46:16 +01:00
for idx, filename in enumerate(filenames):
destfilename = join(self.dest_dir, filename[1:])
2019-12-22 11:04:39 +01:00
makedirs(dirname(destfilename), exist_ok=True)
2019-12-22 14:46:16 +01:00
if variable:
var = variable[idx]
else:
var = None
source = join(self.tmp_dir, filevar['source'])
if filevar['templating']:
self.process(source,
filename,
destfilename,
filevar,
var)
else:
copy(source, destfilename)
2019-12-02 10:31:55 +01:00
2020-02-16 21:27:42 +01:00
async def instance_files(self) -> None:
"""Run templatisation on all files
2019-12-02 10:31:55 +01:00
"""
2020-01-13 19:44:24 +01:00
for option in await self.config.option.list(type='all'):
namespace = await option.option.name()
2020-07-20 18:13:53 +02:00
if namespace == variable_namespace:
2020-07-06 20:58:11 +02:00
await self.load_eole_variables_rougail(option)
2020-01-13 19:44:24 +01:00
else:
families = await self.load_eole_variables(namespace,
option)
self.rougail_variables_dict[namespace] = families
2019-12-02 14:23:02 +01:00
for template in listdir(self.distrib_dir):
self.prepare_template(join(self.distrib_dir, template))
2020-02-14 17:59:39 +01:00
for service_obj in await self.config.option('services').list('all'):
service_name = await service_obj.option.doc()
2020-02-14 17:59:39 +01:00
for fills in await service_obj.list('all'):
if await fills.option.name() in ['files', 'overrides']:
2020-01-13 19:44:24 +01:00
for fill_obj in await fills.list('all'):
fill = await fill_obj.value.dict()
2019-12-02 10:31:55 +01:00
filename = fill['source']
2019-12-02 14:23:02 +01:00
distib_file = join(self.distrib_dir, filename)
if not isfile(distib_file):
raise FileNotFound(_(f"File {distib_file} does not exist."))
if fill.get('activate', False):
2019-12-02 14:23:02 +01:00
self.instance_file(fill,
service_name,
)
2019-12-02 10:31:55 +01:00
else:
log.debug(_("Instantiation of file '{filename}' disabled"))
2020-01-13 19:44:24 +01:00
async def generate(config: Config,
eosfunc_file: str,
distrib_dir: str,
tmp_dir: str,
dest_dir: str,
2020-04-29 14:06:29 +02:00
) -> None:
2019-12-02 10:31:55 +01:00
engine = CreoleTemplateEngine(config,
2019-12-02 14:23:02 +01:00
eosfunc_file,
distrib_dir,
tmp_dir,
dest_dir,
2020-04-29 14:06:29 +02:00
)
2020-02-16 21:27:42 +01:00
await engine.instance_files()