1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
import re
import sys
import logging
import traceback
import Bcfg2.Server.Plugin
from Bcfg2.Server.Plugins.Cfg import CfgGenerator
logger = logging.getLogger(__name__)
try:
import genshi.core
from genshi.template import TemplateLoader, NewTextTemplate
from genshi.template.eval import UndefinedError
have_genshi = True
except ImportError:
TemplateLoader = None
have_genshi = False
# snipped from TGenshi
def removecomment(stream):
"""A genshi filter that removes comments from the stream."""
for kind, data, pos in stream:
if kind is genshi.core.COMMENT:
continue
yield kind, data, pos
class CfgGenshiGenerator(CfgGenerator):
__extensions__ = ['genshi']
__loader_cls__ = TemplateLoader
pyerror_re = re.compile('<\w+ u?[\'"](.*?)\s*\.\.\.[\'"]>')
def __init__(self, fname, spec, encoding):
CfgGenerator.__init__(self, fname, spec, encoding)
if not have_genshi:
msg = "Cfg: Genshi is not available: %s" % fname
logger.error(msg)
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
self.loader = self.__loader_cls__()
self.template = None
@classmethod
def ignore(cls, event, basename=None):
return (event.filename.endswith(".genshi_include") or
CfgGenerator.ignore(event, basename=basename))
def get_data(self, entry, metadata):
fname = entry.get('realname', entry.get('name'))
stream = \
self.template.generate(name=fname,
metadata=metadata,
path=self.name).filter(removecomment)
try:
try:
return stream.render('text', encoding=self.encoding,
strip_whitespace=False)
except TypeError:
return stream.render('text', encoding=self.encoding)
except UndefinedError:
# a failure in a genshi expression _other_ than %{ python ... %}
err = sys.exc_info()[1]
stack = traceback.extract_tb(sys.exc_info()[2])
for quad in stack:
if quad[0] == self.name:
logger.error("Cfg: Error rendering %s at '%s': %s: %s" %
(fname, quad[2], err.__class__.__name__, err))
break
raise
except:
# a failure in a %{ python ... %} block -- the snippet in
# the traceback is just the beginning of the block.
err = sys.exc_info()[1]
stack = traceback.extract_tb(sys.exc_info()[2])
(filename, lineno, func, text) = stack[-1]
# this is horrible, and I deeply apologize to whoever gets
# to maintain this after I go to the Great Beer Garden in
# the Sky. genshi is incredibly opaque about what's being
# executed, so the only way I can find to determine which
# {% python %} block is being executed -- if there are
# multiples -- is to iterate through them and match the
# snippet of the first line that's in the traceback with
# the first non-empty line of the block.
execs = [contents
for etype, contents, loc in self.template.stream
if etype == self.template.EXEC]
contents = None
if len(execs) == 1:
contents = execs[0]
elif len(execs) > 1:
match = pyerror_re.match(func)
if match:
firstline = match.group(0)
for pyblock in execs:
if pyblock.startswith(firstline):
contents = pyblock
break
# else, no EXEC blocks -- WTF?
if contents:
# we now have the bogus block, but we need to get the
# offending line. To get there, we do (line number
# given in the exception) - (firstlineno from the
# internal genshi code object of the snippet) + 1 =
# (line number of the line with an error within the
# block, with all multiple line breaks elided to a
# single line break)
real_lineno = lineno - contents.code.co_firstlineno
src = re.sub(r'\n\n+', '\n', contents.source).splitlines()
logger.error("Cfg: Error rendering %s at '%s': %s: %s" %
(fname, src[real_lineno], err.__class__.__name__,
err))
raise
def handle_event(self, event):
if event.code2str() == 'deleted':
return
CfgGenerator.handle_event(self, event)
try:
self.template = self.loader.load(self.name, cls=NewTextTemplate,
encoding=self.encoding)
except Exception:
msg = "Cfg: Could not load template %s: %s" % (self.name,
sys.exc_info()[1])
logger.error(msg)
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
|