# -*- 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, Any from subprocess import call from os import listdir, unlink, makedirs from os.path import dirname, basename, join, split, isfile, isdir 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 tiramisu.error import PropertiesOptionError from .config import patch_dir from .error import FileNotFound, TemplateError, TemplateDisabled from .i18n import _ from .utils import normalize_family log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) class IsDefined: """ 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: 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, destfilename, variable): """Initialize Creole CheetahTemplate """ 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]) class CreoleLeader: 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, PropertiesOptionError): raise AttributeError() 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 CreoleLeader(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 CreoleLeader(self._value[i], ret, i) def __len__(self): """Delegate to master value """ return len(self._value) def __repr__(self): """Show CreoleLeader 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, config, name, path): if isinstance(self._value, list): 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 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__() class CreoleTemplateEngine: """Engine to process Creole cheetah template """ def __init__(self, config: Config, eosfunc_file: str, distrib_dir: str, tmp_dir: str, dest_dir:str) -> None: self.config = config self.dest_dir = dest_dir self.tmp_dir = tmp_dir self.distrib_dir = distrib_dir 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 = {} for option in self.config.option.list(type='all'): namespace = option.option.name() if namespace == 'containers': continue elif namespace == 'creole': self.load_eole_variables_creole(self.config, option) else: self.load_eole_variables(self.config, namespace, option) def load_eole_variables_creole(self, config, optiondescription): for option in optiondescription.list('all'): if option.option.isoptiondescription(): if option.option.isleadership(): 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()) else: self.load_eole_variables_creole(config, option) else: self.creole_variables_dict[option.option.name()] = option.value.get() 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()) leader_name = suboption.value.name() 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) def patch_template(self, filename: str): """Apply patch to a template """ patch_cmd = ['patch', '-d', self.tmp_dir, '-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, self.tmp_dir) 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}' -> '{self.tmp_dir}'")) copy(filename, self.tmp_dir) self.patch_template(filename) # self.strip_template_comment(filename) def process(self, destfilename: str, filevar: Dict, container: str, variable: Any): """Process a cheetah template """ # full path of the destination file log.info(_(f"Cheetah processing: '{destfilename}'")) try: cheetah_template = CheetahTemplate(join(self.tmp_dir, filevar['source']), self.creole_variables_dict, self.eosfunc, self.config.config.copy(), container, destfilename, variable) 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, destfilename:str, 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}'")) container_dir = join(self.dest_dir, container) filenames = filevar['name'] if 'variable' in filevar: variable = filevar['variable'] else: variable = None if not isinstance(filenames, list): filenames = [filenames] if variable: variable = [variable] for idx, filename in enumerate(filenames): destfilename = join(container_dir, filename[1:]) makedirs(dirname(destfilename), exist_ok=True) if variable: var = variable[idx] else: var = None self.process(destfilename, filevar, container, var) self.change_properties(destfilename, 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(self.distrib_dir): self.prepare_template(join(self.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'] 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): self.instance_file(fill, current_container) else: log.debug(_("Instantiation of file '{filename}' disabled")) def generate(config: Config, eosfunc_file: str, distrib_dir: str, tmp_dir: str, dest_dir: str, container: str=None): engine = CreoleTemplateEngine(config, eosfunc_file, distrib_dir, tmp_dir, dest_dir) engine.instance_files(container=container)