scripts/bcfg2/pygen.py
Jeremie Dimino 1ce640fa74 Ameliorations.
darcs-hash:20071218042604-af139-5eb497a883be45bcff74c2fecfcd0f7b4ea4818b.gz
2007-12-18 05:26:04 +01:00

167 lines
6 KiB
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'''
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'''
# 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)
"export_definition": self.export_definition,
# 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["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%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(variable):
'''Exporte une variable'''
self["exports"].add(variable)
__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))
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)
sp = line.split("=", 1)
if len(sp) == 1:
sp = line.split(" ", 1)
newsource.write("export_definition('")
newsource.write(sp[0])
newsource.write("', tostring(")
newsource.write(sp[1])
newsource.write("))\n")
varname = sp[0].strip()
else:
newsource.write(line)
newsource.write("; export_definition('")
newsource.write(sp[0])
newsource.write("', tostring(")
newsource.write(sp[0])
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