rougail/src/rougail/template.py

514 lines
18 KiB
Python
Raw 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
import sys
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
2019-12-22 11:04:39 +01:00
from os import listdir, unlink, makedirs
from os.path import dirname, basename, join, split, isfile, isdir
2019-12-02 10:31:55 +01:00
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
2019-12-20 11:02:26 +01:00
from tiramisu.error import PropertiesOptionError
2019-12-02 10:31:55 +01:00
2019-12-02 14:23:02 +01:00
from .config import patch_dir
2019-12-02 10:31:55 +01:00
from .error import FileNotFound, TemplateError, TemplateDisabled
from .i18n import _
from .utils import normalize_family
2019-12-02 10:31:55 +01:00
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
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:
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
2019-12-20 11:02:26 +01:00
class CreoleGet:
2019-12-02 10:31:55 +01:00
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' : '%',
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 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,
2019-12-22 11:04:39 +01:00
current_container: str,
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),
# 'creole_client' : CreoleClient(config),
# 'current_container':CreoleGet(current_container),
'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:
2019-12-02 10:31:55 +01:00
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]
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):
"""Get a master.slave at requested index.
"""
ret = {}
for key, values in self.slave.items():
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):
"""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]
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):
"""Delegate to master value
"""
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
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
2019-12-20 11:02:26 +01:00
def add_slave(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:
values.append(config.option(path, idx).value.get())
except PropertiesOptionError as err:
values.append(err)
else:
raise Exception('hu?')
self.slave[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__()
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 = {}
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 = {}
2019-12-22 15:35:50 +01:00
for option in self.config.option.list(type='all'):
namespace = option.option.name()
2019-12-22 15:54:41 +01:00
if namespace in ['containers', 'actions']:
2019-12-22 15:35:50 +01:00
continue
elif namespace == 'creole':
self.load_eole_variables_creole(self.config,
option)
else:
self.load_eole_variables(self.config,
namespace,
option)
2019-12-02 10:31:55 +01:00
2019-12-22 15:35:50 +01:00
def load_eole_variables_creole(self,
config,
optiondescription):
2019-12-02 10:31:55 +01:00
for option in optiondescription.list('all'):
if option.option.isoptiondescription():
if option.option.isleadership():
2019-12-20 11:02:26 +01:00
for idx, suboption in enumerate(option.list('all')):
if idx == 0:
leader = CreoleLeader(suboption.value.get())
self.creole_variables_dict[suboption.option.name()] = leader
else:
leader.add_slave(config,
suboption.option.name(),
suboption.option.path())
2019-12-02 10:31:55 +01:00
else:
2019-12-22 15:35:50 +01:00
self.load_eole_variables_creole(config,
option)
2019-12-02 10:31:55 +01:00
else:
self.creole_variables_dict[option.option.name()] = option.value.get()
2019-12-22 15:35:50 +01:00
def load_eole_variables(self,
config,
namespace,
optiondescription):
families = {}
for family in optiondescription.list('all'):
variables = {}
for variable in family.list('all'):
if variable.option.isoptiondescription() and variable.option.isleadership():
for idx, suboption in enumerate(variable.list('all')):
if idx == 0:
leader = CreoleLeader(suboption.value.get())
2019-12-22 15:49:21 +01:00
leader_name = suboption.option.name()
2019-12-22 15:35:50 +01:00
else:
leader.add_slave(config,
suboption.option.name(),
suboption.option.path())
variables[leader_name] = leader
else:
variables[variable.option.name()] = variable.value.get()
families[family.option.name()] = CreoleExtra(variables)
self.creole_variables_dict[namespace] = CreoleExtra(families)
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 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
"""
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)
# self.strip_template_comment(filename)
def process(self,
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
container: str,
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:
2019-12-22 11:04:39 +01:00
cheetah_template = CheetahTemplate(join(self.tmp_dir,
filevar['source']),
2019-12-02 10:31:55 +01:00
self.creole_variables_dict,
self.eosfunc,
self.config.config.copy(),
2019-12-22 11:04:39 +01:00
container,
2019-12-22 14:46:16 +01:00
destfilename,
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 change_properties(self,
2019-12-02 14:23:02 +01:00
destfilename:str,
2019-12-02 10:31:55 +01:00
filevar: Dict):
#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}'"))
2019-12-02 14:23:02 +01:00
container_dir = join(self.dest_dir,
container)
2019-12-22 11:04:39 +01:00
filenames = filevar['name']
2019-12-22 14:46:16 +01:00
if 'variable' in filevar:
variable = filevar['variable']
else:
variable = None
2019-12-22 11:04:39 +01:00
if not isinstance(filenames, list):
filenames = [filenames]
2019-12-22 14:46:16 +01:00
if variable:
variable = [variable]
for idx, filename in enumerate(filenames):
2019-12-22 11:04:39 +01:00
destfilename = join(container_dir,
filename[1:])
makedirs(dirname(destfilename), exist_ok=True)
2019-12-22 14:46:16 +01:00
if variable:
var = variable[idx]
else:
var = None
2019-12-22 11:04:39 +01:00
self.process(destfilename,
filevar,
2019-12-22 14:46:16 +01:00
container,
var)
2019-12-22 11:04:39 +01:00
self.change_properties(destfilename,
filevar)
2019-12-02 10:31:55 +01:00
def instance_files(self,
container=None):
"""Run templatisation on all files of all containers
@param container: name of a container
@type container: C{str}
"""
2019-12-02 14:23:02 +01:00
for template in listdir(self.distrib_dir):
self.prepare_template(join(self.distrib_dir, template))
2019-12-02 10:31:55 +01:00
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']
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,
current_container)
2019-12-02 10:31:55 +01:00
else:
log.debug(_("Instantiation of file '{filename}' disabled"))
def generate(config: Config,
eosfunc_file: str,
2019-12-02 14:23:02 +01:00
distrib_dir: str,
tmp_dir: str,
dest_dir: str,
2019-12-02 10:31:55 +01:00
container: str=None):
engine = CreoleTemplateEngine(config,
2019-12-02 14:23:02 +01:00
eosfunc_file,
distrib_dir,
tmp_dir,
dest_dir)
2019-12-02 10:31:55 +01:00
engine.instance_files(container=container)