# -*- mode: python; coding: utf-8 -*- # # pygen.py # -------- # # Copyright (C) 2007 Jeremie Dimino # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This file is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. '''Module utilisé par le plugin Python de bcfg2 pour générer un fichier de configuration à partir d'un script python''' import re, marshal, os from cStringIO import StringIO # Pour l'arrêt d'un script class Done(Exception): pass def done(): raise Done() class Environment(dict): '''Environment dans lequel sont éxécuté les scripts''' # Dernière variable exportée à avoir été définie last_definition = None # Le flux de sortie stream = None # Les variables qui sont partagées entre le script éxécuté # et le programme appelant shared = ["out", "exports", "export_definition", "last_definition", "conv", "tostring"] def __init__(self): # Les variables à "exporter" dans le fichier produit self.exports = {} # Fonctions de convertions initiales self.conv = {bool: {True: "yes", False: "no"}, list: lambda l: ", ".join([str(x) for x in l]), tuple: lambda l: ", ".join([str(x) for x in l])} # Création de l'environment initial dict.__init__(self, { # Permet d'accéder aux variables exportées "exports": self.exports, "export": self.export, # On met a disposition le nécessaire pour surcharger # la gestion des variables exportée "last_definition": self.last_definition, "export_definition": self.export_definition, # La convertion "conv": self.conv, "tostring": self.tostring, # Fonction de base pour imprimer quelque chose "out": self.out, # Arrêt du script "done": done}) def __setitem__(self, variable, value): '''Lorsqu'on définie une variable, on "l'exporte" dans le fichier produit''' # if variable in self.shared: # self.__setattr__(variable, value) if variable in self["exports"]: self["export_definition"](variable, value) dict.__setitem__(self, "last_definition", variable) dict.__setitem__(self, variable, value) def export_definition(self, variable, value): '''Fonction par défaut pour gérée la définition des variables exportée. écrit 'variable = tostring(value)' dans le fichier produit''' self["out"]("%s = %s\n" % (variable, self["tostring"](value))) def out(self, string): '''Fonction de base pour écrire une chaine dans le fichier produit''' self.stream.write(string) def tostring(self, value): '''Fonction de convertion objet python -> chaine dans un format sympa''' convertor = self["conv"].get(type(value)) if convertor: if type(convertor) == dict: return convertor[value] else: return convertor(value) else: return str(value) # Cette fonction pourrait être écrite directement dans les scripts # mais elle est pratique donc on la met pour tout le monde def export(variable): '''Exporte une variable''' self["exports"][variable] = True __re_special_line = re.compile(r"^([ \t]*)@(.*)$", re.MULTILINE) def compileSource(source, filename=""): '''Compile un script''' # On commence par remplacer les lignes de la forme # @xxx par out("xxx") newsource = StringIO() start = 0 for m in __re_special_line.finditer(source): newsource.write(source[start:m.start()]) newsource.write(m.group(1)) newsource.write('out("') newsource.write(m.group(2).replace("\\", "\\\\").replace('"', '\\"')) newsource.write('\\n")') start = m.end() newsource.write(source[start:]) return compile(newsource.getvalue(), filename, "exec") def generate(code, environment=None): '''Évalue un script''' if type(code) == str: code = compileSource(code) if not environment: environment = Environment() environment.stream = StringIO() try: exec(code, environment) except Done, _: pass return environment.stream.getvalue() def load(fname, cfname=None): '''Charge un script et le compile, en créant/utilisant un fichier de cache''' if not cfname: cfname = fname + 'c' if os.path.exists(cfname) and os.stat(fname).st_mtime <= os.stat(cfname).st_mtime: code = marshal.load(file(cfname)) else: code = compileSource(file(fname).read(), fname) marshal.dump(code, open(cfname, "w")) return code