On remet bcfg2_reports et bcfg2-graph, et on renomme le nouveau dossier
This commit is contained in:
parent
1df9d480af
commit
08007c623e
10 changed files with 0 additions and 0 deletions
221
bcfg2/Plugins/Python/PythonFile.py
Normal file
221
bcfg2/Plugins/Python/PythonFile.py
Normal file
|
@ -0,0 +1,221 @@
|
|||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Fournit une couche d'abstraction Python pour les fichiers du même
|
||||
nom"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import marshal
|
||||
import cStringIO
|
||||
|
||||
from Bcfg2.Server.Plugin import Debuggable
|
||||
|
||||
from .PythonFactories import PythonFileFactory
|
||||
import PythonEnv
|
||||
import PythonTools
|
||||
|
||||
__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]+=?(.*)")
|
||||
|
||||
class PythonFile(Debuggable):
|
||||
"""Classe représentant un fichier Python"""
|
||||
|
||||
#: Permet de savoir si l'instance a déjà été initialisée
|
||||
initialized = False
|
||||
|
||||
def __new__(cls, path, parent=None):
|
||||
"""Si le fichier a déjà été enregistré dans la Factory, on
|
||||
le retourne, et on évite de réinstancier la classe.
|
||||
|
||||
path est le chemin absolu du fichier"""
|
||||
|
||||
path = os.path.normpath(path)
|
||||
|
||||
file_instance = PythonFileFactory.get(path)
|
||||
if file_instance is None:
|
||||
file_instance = super(PythonFile, cls).__new__(cls)
|
||||
PythonFileFactory.record(path, file_instance)
|
||||
|
||||
return file_instance
|
||||
|
||||
def __init__(self, path, parent=None):
|
||||
"""Initialisation, si non déjà faite"""
|
||||
|
||||
if self.initialized:
|
||||
return
|
||||
|
||||
super(self.__class__, self).__init__()
|
||||
|
||||
#: A string containing the raw data in this file
|
||||
self.data = None
|
||||
|
||||
#: Le chemin complet du fichier
|
||||
self.path = os.path.normpath(path)
|
||||
|
||||
#: Le nom du fichier
|
||||
self.name = os.path.basename(self.path)
|
||||
|
||||
#: Un logger
|
||||
self.logger = PythonTools.LOGGER
|
||||
|
||||
#: Le plugin parent est pointé pour des raisons pratiques
|
||||
self.parent = parent
|
||||
|
||||
#: C'est bon, c'est initialisé
|
||||
self.initialized = True
|
||||
|
||||
def exists(self):
|
||||
"""Teste l'existence du fichier"""
|
||||
return os.path.exists(self.path)
|
||||
|
||||
def HandleEvent(self, event=None):
|
||||
""" HandleEvent is called whenever the FAM registers an event.
|
||||
|
||||
:param event: The event object
|
||||
:type event: Bcfg2.Server.FileMonitor.Event
|
||||
:returns: None
|
||||
"""
|
||||
if event and event.code2str() not in ['exists', 'changed', 'created']:
|
||||
return
|
||||
|
||||
try:
|
||||
self.load()
|
||||
except IOError:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to read file %s: %s" % (self.name, err))
|
||||
except:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to parse file %s: %s" % (self.name, err))
|
||||
|
||||
def __repr__(self):
|
||||
return "%s: %s" % (self.__class__.__name__, self.name)
|
||||
|
||||
def load(self, refresh=True):
|
||||
"""Charge le fichier"""
|
||||
if self.data is not None and not refresh:
|
||||
return
|
||||
|
||||
try:
|
||||
directory = os.path.dirname(self.path)
|
||||
compiled_file = os.path.join(directory, ".%s.COMPILED" % (self.name,))
|
||||
|
||||
if os.path.exists(compiled_file) and os.stat(self.path).st_mtime <= os.stat(compiled_file).st_mtime:
|
||||
self.data = marshal.load(open(compiled_file, 'r'))
|
||||
else:
|
||||
self.data = compileSource(open(self.path, 'r').read(), self.path, self.logger)
|
||||
cfile = open(compiled_file, "w")
|
||||
marshal.dump(self.data, cfile)
|
||||
cfile.close()
|
||||
except Exception as error:
|
||||
PythonTools.log_traceback(self.path, 'compilation', error, self.logger)
|
||||
|
||||
def run(self, additionnal=None, environment=None):
|
||||
"""Exécute le code"""
|
||||
if self.data is None:
|
||||
self.load(True)
|
||||
|
||||
if additionnal is None:
|
||||
additionnal = {}
|
||||
|
||||
if environment is None:
|
||||
environment = PythonEnv.SafeEnvironment(additionnal, self)
|
||||
|
||||
# Lors de l'exécution d'un fichier, on inclut
|
||||
# toujours common (ie on l'exécute dans l'environnement)
|
||||
environment.include("common")
|
||||
|
||||
try:
|
||||
exec(self.data, environment)
|
||||
except Exception:
|
||||
sys.stderr.write('code: %r\n' % (self.data,))
|
||||
raise
|
||||
|
||||
return environment.stream.getvalue(), environment['info']
|
||||
|
||||
#+---------------------------------------------+
|
||||
#| Tools for compilation |
|
||||
#+---------------------------------------------+
|
||||
|
||||
def compileSource(source, filename="", logger=None):
|
||||
'''Compile un script'''
|
||||
# On commence par remplacer les lignes de la forme
|
||||
# @xxx par out("xxx")
|
||||
newsource = cStringIO.StringIO()
|
||||
start = 0
|
||||
|
||||
# Parsing de goret : on boucle sur les lignes spéciales,
|
||||
# c'est-à-dire celles commençant par un @ ou un % précédé
|
||||
# par d'éventuelles espaces/tabs.
|
||||
for match in __RE_SPECIAL_LINE.finditer(source):
|
||||
# On prend tout ce qui ne nous intéresse pas et on l'ajoute.
|
||||
newsource.write(source[start:match.start()])
|
||||
|
||||
# On redéfinit start.
|
||||
start = match.end()
|
||||
|
||||
# On écrit le premier groupe (les espaces et cie)
|
||||
newsource.write(match.group(1))
|
||||
|
||||
# Le linetype est soit @ soit %
|
||||
linetype = match.group(2)
|
||||
|
||||
# @ c'est du print.
|
||||
if linetype == "@":
|
||||
# On prend ce qui nous intéresse, et on fait quelques remplacements
|
||||
# pour éviter les plantages.
|
||||
line = match.group(3).replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
# Si la ligne est un commentaire, on la reproduit en remplaçant éventuellement
|
||||
# le # par le bon caractère.
|
||||
if line and line[0] == "#":
|
||||
newsource.write('out(comment_start + "')
|
||||
line = line[1:]
|
||||
|
||||
# Sinon bah....
|
||||
else:
|
||||
newsource.write('out("')
|
||||
|
||||
# On écrit ladite ligne
|
||||
newsource.write(line)
|
||||
|
||||
# Et un superbe \n.
|
||||
newsource.write('")')
|
||||
|
||||
# %, affectation.
|
||||
elif linetype == "%":
|
||||
# On récupère le reste.
|
||||
line = match.group(3)
|
||||
|
||||
# On fait du matching clef/valeur
|
||||
match = __RE_AFFECTATION.match(line)
|
||||
if match:
|
||||
# Le nom est le premier groupe.
|
||||
# Et après c'est weird...
|
||||
varname = match.group(1)
|
||||
newsource.write(line)
|
||||
newsource.write("; defvar('")
|
||||
newsource.write(varname)
|
||||
newsource.write("', tostring(")
|
||||
newsource.write(varname)
|
||||
newsource.write("))\n")
|
||||
else:
|
||||
# Pareil, sauf que cette fois, ce qu'on fait a un sens.
|
||||
match = __RE_SPACE_SEP.match(line)
|
||||
newsource.write("defvar('")
|
||||
newsource.write(match.group(1))
|
||||
# Le tostring est facultatif.
|
||||
newsource.write("', tostring(")
|
||||
newsource.write(match.group(2))
|
||||
newsource.write("))\n")
|
||||
# On continue.
|
||||
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")
|
Loading…
Add table
Add a link
Reference in a new issue