rougail/src/rougail/template.py

443 lines
15 KiB
Python

# -*- coding: utf-8 -*-
"""
Gestion du mini-langage de template
On travaille sur les fichiers cibles
"""
import imp
import sys
from shutil import copy
import logging
from typing import Dict
from subprocess import call
from os import listdir, unlink
from os.path import basename, join, split, isfile
from tempfile import mktemp
from Cheetah import Parser
# l'encoding du template est déterminé par une regexp (encodingDirectiveRE dans Parser.py)
# il cherche un ligne qui ressemble à '#encoding: utf-8
# cette classe simule le module 're' et retourne toujours l'encoding utf-8
# 6224
class FakeEncoding():
def groups(self):
return ('utf-8',)
def search(self, *args):
return self
Parser.encodingDirectiveRE = FakeEncoding()
from Cheetah.Template import Template as ChtTemplate
from Cheetah.NameMapper import NotFound as CheetahNotFound
from tiramisu import Config
from .config import patch_dir, templatedir, distrib_dir
from .error import FileNotFound, TemplateError, TemplateDisabled
from .i18n import _
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
class IsDefined(object):
"""
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:
msg = _("Group variables must be of type master.slave")
raise KeyError(msg)
master, slave = splitted_var
if master in self.context:
return slave in self.context[master].slave.keys()
return False
else:
return varname in self.context
class CreoleGet(object):
def __init__(self, context):
self.context = context
def __call__(self, varname):
return self.context[varname]
def __getitem__(self, varname):
"""For bracket and dotted notation
"""
return self.context[varname]
def __contains__(self, varname):
"""Check variable existence in context
"""
return varname in self.context
@classmethod
def cl_compile(kls, *args, **kwargs):
kwargs['compilerSettings'] = {'directiveStartToken' : '%',
'cheetahVarStartToken' : '%%',
'EOLSlurpToken' : '%',
'PSPStartToken' : 'µ' * 10,
'PSPEndToken' : 'µ' * 10,
'commentStartToken' : 'µ' * 10,
'commentEndToken' : 'µ' * 10,
'multiLineCommentStartToken' : 'µ' * 10,
'multiLineCommentEndToken' : 'µ' * 10}
return kls.old_compile(*args, **kwargs)
ChtTemplate.old_compile = ChtTemplate.compile
ChtTemplate.compile = cl_compile
class CreoleClient:
def __init__(self,
config: Config):
self.config = config
class CheetahTemplate(ChtTemplate):
"""classe pour personnaliser et faciliter la construction
du template Cheetah
"""
def __init__(self,
filename: str,
context,
eosfunc: Dict,
config: Config,
current_container: str):
"""Initialize Creole CheetahTemplate
"""
ChtTemplate.__init__(self, file=filename,
searchList=[context, eosfunc, {'is_defined' : IsDefined(context),
'creole_client' : CreoleClient(config),
'current_container':CreoleGet(current_container),
}])
class CreoleMaster(object):
def __init__(self, value, slave=None, index=None):
"""
On rend la variable itérable pour pouvoir faire:
for ip in iplist:
print ip.network
print ip.netmask
print ip
index is used for CreoleLint
"""
self._value = value
if slave is not None:
self.slave = slave
else:
self.slave = {}
self._index = index
def __getattr__(self, name):
"""Get slave variable or attribute of master value.
If the attribute is a name of a slave variable, return its value.
Otherwise, returns the requested attribute of master value.
"""
if name in self.slave:
value = self.slave[name]
if isinstance(value, Exception):
raise value
return value
else:
return getattr(self._value, name)
def __getitem__(self, index):
"""Get a master.slave at requested index.
"""
ret = {}
for key, values in self.slave.items():
ret[key] = values[index]
return CreoleMaster(self._value[index], ret, index)
def __iter__(self):
"""Iterate over master.slave.
Return synchronised value of master.slave.
"""
for i in range(len(self._value)):
ret = {}
for key, values in self.slave.items():
ret[key] = values[i]
yield CreoleMaster(self._value[i], ret, i)
def __len__(self):
"""Delegate to master value
"""
return len(self._value)
def __repr__(self):
"""Show CreoleMaster as dictionary.
The master value is stored under 'value' key.
The slaves are stored under 'slave' key.
"""
return repr({'value': self._value, 'slave': self.slave})
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):
"""Delegate to master value
"""
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
def add_slave(self, name, value):
"""Add a slave variable
Minimal check on type and value of the slave in regards to the
master one.
@param name: name of the slave variable
@type name: C{str}
@param value: value of the slave variable
"""
if isinstance(self._value, list):
if not isinstance(value, list):
raise TypeError
elif len(value) != len(self._value):
raise ValueError(_('length mismatch'))
new_value = []
for val in value:
if isinstance(val, dict):
new_value.append(ValueError(val['err']))
else:
new_value.append(val)
value = new_value
elif isinstance(value, list):
raise TypeError
self.slave[name] = value
class CreoleTemplateEngine:
"""Engine to process Creole cheetah template
"""
def __init__(self,
config: Config,
eosfunc_file: str):
self.config = config
eos = {}
eosfunc = imp.load_source('eosfunc', eosfunc_file)
for func in dir(eosfunc):
if not func.startswith('_'):
eos[func] = getattr(eosfunc, func)
self.eosfunc = eos
self.creole_variables_dict = {}
self.load_eole_variables(self.config.option('creole'))
def load_eole_variables(self, optiondescription):
# remplacement des variables EOLE
for option in optiondescription.list('all'):
if option.option.isoptiondescription():
if option.option.isleadership():
print('leadership')
raise Exception('a faire')
else:
self.load_eole_variables(option)
else:
self.creole_variables_dict[option.option.name()] = option.value.get()
#if varname.find('.') != -1:
# #support des groupes
# mastername, slavename = varname.split('.')
# if not mastername in self.creole_variables_dict or not \
# isinstance(self.creole_variables_dict [mastername],
# CreoleMaster):
# # Create the master variable
# if mastername in values:
# self.creole_variables_dict[mastername] = CreoleMaster(values[mastername])
# else:
# #only for CreoleLint
# self.creole_variables_dict[mastername] = CreoleMaster(value)
# #test only for CreoleLint
# if mastername != slavename:
# self.creole_variables_dict[mastername].add_slave(slavename, value)
#else:
# self.creole_variables_dict[varname] = value
def patch_template(self,
filename: str):
"""Apply patch to a template
"""
patch_cmd = ['patch', '-d', templatedir, '-N', '-p1']
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}"))
copy(filename, templatedir)
def strip_template_comment(self,
filename: str):
"""Strip comment from template
This apply if filevar has a del_comment attribut
"""
# suppression des commentaires si demandé (attribut del_comment)
if 'del_comment' in filevar and filevar['del_comment'] != '':
strip_cmd = ['sed', '-i']
log.info(_("Cleaning file '{0}'").format( filevar['source'] ))
raise Exception('hu')
#ret, out, err = pyeole.process.system_out(strip_cmd
# + ['/^\s*{0}/d ; /^$/d'.format(filevar['del_comment']),
# filevar['source'] ])
#if ret != 0:
# msg = _("Error removing comments '{0}': {1}")
# raise TemplateError(msg.format(filevar['del_comment'], err))
def prepare_template(self,
filename: str):
"""Prepare template source file
"""
log.info(_("Copy template: '{filename}' -> '{templatedir}'"))
copy(filename, templatedir)
self.patch_template(filename)
# self.strip_template_comment(filename)
def process(self,
filevar: Dict,
container: str):
"""Process a cheetah template
"""
# full path of the destination file
destfilename = join(dest_dir, filevar['source'])
log.info(_(f"Cheetah processing: '{destfilename}'"))
try:
cheetah_template = CheetahTemplate(join(templatedir, filevar['source']),
self.creole_variables_dict,
self.eosfunc,
self.config.config.copy(),
container)
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 change_properties(self,
filevar: Dict):
destfilename = join(dest_dir, filevar['source'])
#chowncmd = ['chown']
#chownarg = ''
chmodcmd = ['chmod']
chmodarg = ''
#if 'owner' in filevar and filevar['owner']:
# chownarg = filevar['owner']
#else:
# chownarg = 'root'
#if 'group' in filevar and filevar['group']:
# chownarg += ":" + filevar['group']
#else:
# chownarg += ':root'
if 'mode' in filevar and filevar['mode']:
chmodarg = filevar['mode']
else:
chmodarg = '0644'
#chowncmd.extend( [chownarg, destfilename] )
chmodcmd.extend([chmodarg, destfilename])
#log.info(_('Changing properties: {0}').format(' '.join(chowncmd)) )
#ret = call(chowncmd)
#if ret:
# log.error(_('Error changing properties {0}: {1}').format(ret, err) )
log.info(_('Changing properties: {0}').format(' '.join(chmodcmd)) )
ret = call(chmodcmd)
if ret:
chmod_cmd = ' '.join(chmodcmd)
log.error(_(f'Error changing properties: {chmodcmd}'))
def instance_file(self,
filevar: Dict,
container: str):
"""Run templatisation on one file of one container
"""
log.info(_("Instantiating file '{filename}'"))
self.process(filevar,
container)
self.change_properties(filevar)
def instance_files(self,
container=None):
"""Run templatisation on all files of all containers
@param container: name of a container
@type container: C{str}
"""
for template in listdir(distrib_dir):
self.prepare_template(join(distrib_dir, template))
for container_obj in self.config.option('containers').list('all'):
current_container = container_obj.option.doc()
if container is not None and container != current_container:
continue
for fills in container_obj.list('all'):
if fills.option.name() == 'files':
for fill_obj in fills.list('all'):
fill = fill_obj.value.dict()
filename = fill['source']
if not isfile(join(distrib_dir, filename)):
raise FileNotFound(_(f"File {filename} does not exist."))
print(fill)
if fill['activate']:
self.instance_file(fill, current_container)
else:
log.debug(_("Instantiation of file '{filename}' disabled"))
def generate(config: Config,
eosfunc_file: str,
container: str=None):
engine = CreoleTemplateEngine(config,
eosfunc_file)
engine.instance_files(container=container)