194 lines
6.9 KiB
Python
Executable file
194 lines
6.9 KiB
Python
Executable file
#!/usr/bin/env python
|
|
# -*- mode: python; coding: utf-8 -*-
|
|
#
|
|
# pygen.py
|
|
# --------
|
|
#
|
|
# Copyright (C) 2007 Jeremie Dimino <jeremie@dimino.org>
|
|
#
|
|
# 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'''
|
|
|
|
__all__ = [ "Environment", "compileSource", "generate", "load" ]
|
|
|
|
import re, marshal, os, sys
|
|
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'''
|
|
|
|
# Le flux de sortie
|
|
stream = None
|
|
|
|
def __init__(self):
|
|
# Création de l'environment initial
|
|
dict.__init__(self, {
|
|
# Les variables à "exporter" dans le fichier produit
|
|
"exports": set([]),
|
|
# Export d'une variable
|
|
"export": self.export,
|
|
# Dernière variable exportée à avoir été définie
|
|
"last_definition": None,
|
|
# Écrit: variable keysep tostring(value)
|
|
"defvar": self.defvar,
|
|
# La convertion en chaîne de charactère
|
|
"tostring": self.tostring,
|
|
# Définition des convertions
|
|
"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])},
|
|
# Fonction de base pour imprimer quelque chose
|
|
"out": self.out,
|
|
# Arrêt du script
|
|
"done": done,
|
|
# Le séparateur pour la forme: variable keysep valeur
|
|
"keysep": "=",
|
|
# Le charactère de commentaire
|
|
"comment_start": "#"})
|
|
|
|
def __setitem__(self, variable, value):
|
|
'''Lorsqu'on définie une variable, on "l'exporte" dans
|
|
le fichier produit'''
|
|
if variable in self["exports"]:
|
|
self["defvar"](variable, value)
|
|
dict.__setitem__(self, "last_definition", variable)
|
|
dict.__setitem__(self, variable, value)
|
|
|
|
def defvar(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%s\n" % (variable, self["keysep"], 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(self, variable):
|
|
'''Exporte une variable'''
|
|
self["exports"].add(variable)
|
|
|
|
__re_special_line = re.compile(r"^([ \t]*)(@|%)(.*)$", re.MULTILINE)
|
|
__re_affectation = re.compile(r"([a-zA-Z_][a-zA-Z_0-9]*)[ \t]*=")
|
|
__re_space_sep = re.compile(r"([^ \t]*)[ \t]+=?(.*)")
|
|
|
|
def compileSource(source, filename="", logger = None):
|
|
'''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()])
|
|
start = m.end()
|
|
newsource.write(m.group(1))
|
|
linetype = m.group(2)
|
|
if linetype == "@":
|
|
line = m.group(3).replace("\\", "\\\\").replace('"', '\\"')
|
|
if line and line[0] == "#":
|
|
newsource.write('out(comment_start + "')
|
|
line = line[1:]
|
|
else:
|
|
newsource.write('out("')
|
|
newsource.write(line)
|
|
newsource.write('\\n")')
|
|
elif linetype == "%":
|
|
line = m.group(3)
|
|
m = __re_affectation.match(line)
|
|
if m:
|
|
varname = m.group(1)
|
|
newsource.write(line)
|
|
newsource.write("; defvar('")
|
|
newsource.write(varname)
|
|
newsource.write("', tostring(")
|
|
newsource.write(varname)
|
|
newsource.write("))\n")
|
|
else:
|
|
m = __re_space_sep.match(line)
|
|
newsource.write("defvar('")
|
|
newsource.write(m.group(1))
|
|
newsource.write("', tostring(")
|
|
newsource.write(m.group(2))
|
|
newsource.write("))\n")
|
|
newsource.write(source[start:])
|
|
if logger:
|
|
try:
|
|
logger.info(newsource.getvalue())
|
|
except:
|
|
print "Le logger de BCFG2 c'est de la merde, il refuse le non ascii."
|
|
print "Voici ce que j'ai essayé de logguer."
|
|
print newsource.getvalue()
|
|
return compile(newsource.getvalue(), filename, "exec")
|
|
|
|
def generate(code, environment=None, logger = None):
|
|
'''Évalue un script'''
|
|
if type(code) == str:
|
|
code = compileSource(code, logger = logger)
|
|
if not environment:
|
|
environment = Environment()
|
|
environment.stream = StringIO()
|
|
save_stdout = sys.stdout
|
|
sys.stdout = environment.stream
|
|
try:
|
|
exec(code, environment)
|
|
except Done, _:
|
|
pass
|
|
except Exception, exn:
|
|
print code
|
|
sys.stdout = save_stdout
|
|
raise
|
|
sys.stdout = save_stdout
|
|
return environment.stream.getvalue()
|
|
|
|
def load(fname, cfname=None, logger=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, logger)
|
|
cfile = open(cfname, "w")
|
|
marshal.dump(code, cfile)
|
|
cfile.close()
|
|
return code
|
|
|
|
if __name__ == "__main__":
|
|
result = generate(load(sys.argv[1]))
|
|
|
|
print "resultat:"
|
|
sys.stdout.write(result)
|