# -*- 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)