# HG changeset patch # User cmlenz # Date 1159349237 0 # Node ID 186d185541839a9cc62b3bb7cbbfbafa5818d26f # Parent 22842c0ca17613e1d8d87d0355092a562c21ef9c Ported [330],[333], and [334] to 0.3.x stable branch. diff --git a/examples/turbogears/genshitest/controllers.py b/examples/turbogears/genshitest/controllers.py --- a/examples/turbogears/genshitest/controllers.py +++ b/examples/turbogears/genshitest/controllers.py @@ -23,6 +23,11 @@ "Genshi", rows=5, cols=40)) + @expose(template="genshi-text:genshitest.templates.plain", + content_type='text/plain; charset=utf-8') + def plain(self): + return dict(name='world') + @expose(template="genshitest.templates.login") def login(self, forward_url=None, previous_url=None, *args, **kw): diff --git a/examples/turbogears/genshitest/templates/plain.txt b/examples/turbogears/genshitest/templates/plain.txt new file mode 100644 --- /dev/null +++ b/examples/turbogears/genshitest/templates/plain.txt @@ -0,0 +1,8 @@ +#choose + #when name + Hello, $name! + #end + #otherwise + Hello, anonymous! + #end +#end diff --git a/examples/turbogears/genshitest/templates/welcome.html b/examples/turbogears/genshitest/templates/welcome.html --- a/examples/turbogears/genshitest/templates/welcome.html +++ b/examples/turbogears/genshitest/templates/welcome.html @@ -33,5 +33,7 @@ ${ET(widget.display())} +

Here's a link to the output of a plain-text template.

+ diff --git a/genshi/eval.py b/genshi/eval.py --- a/genshi/eval.py +++ b/genshi/eval.py @@ -91,7 +91,8 @@ retval = eval(self.code, {'data': data, '_lookup_name': _lookup_name, '_lookup_attr': _lookup_attr, - '_lookup_item': _lookup_item}) + '_lookup_item': _lookup_item}, + {'data': data}) if not nocall and type(retval) is not Undefined and callable(retval): retval = retval() return retval @@ -295,17 +296,33 @@ visitAssName = visitAssTuple = _visitDefault visitConst = visitName = _visitDefault - def visitKeyword(self, node, *args, **kwargs): - node.expr = self.visit(node.expr, *args, **kwargs) - return node - def visitDict(self, node, *args, **kwargs): node.items = [(self.visit(k, *args, **kwargs), self.visit(v, *args, **kwargs)) for k, v in node.items] return node - def visitTuple(self, node, *args, **kwargs): - node.nodes = [self.visit(n, *args, **kwargs) for n in node.nodes] + def visitGenExpr(self, node, *args, **kwargs): + node.code = self.visit(node.code, *args, **kwargs) + node.filename = '' # workaround for bug in pycodegen + return node + + def visitGenExprFor(self, node, *args, **kwargs): + node.assign = self.visit(node.assign, *args, **kwargs) + node.iter = self.visit(node.iter, *args, **kwargs) + node.ifs = [self.visit(x, *args, **kwargs) for x in node.ifs] + return node + + def visitGenExprIf(self, node, *args, **kwargs): + node.test = self.visit(node.test, *args, **kwargs) + return node + + def visitGenExprInner(self, node, *args, **kwargs): + node.expr = self.visit(node.expr, *args, **kwargs) + node.quals = [self.visit(x, *args, **kwargs) for x in node.quals] + return node + + def visitKeyword(self, node, *args, **kwargs): + node.expr = self.visit(node.expr, *args, **kwargs) return node def visitList(self, node, *args, **kwargs): @@ -327,26 +344,6 @@ node.test = self.visit(node.test, *args, **kwargs) return node - def visitGenExpr(self, node, *args, **kwargs): - node.code = self.visit(node.code, *args, **kwargs) - node.filename = '' # workaround for bug in pycodegen - return node - - def visitGenExprFor(self, node, *args, **kwargs): - node.assign = self.visit(node.assign, *args, **kwargs) - node.iter = self.visit(node.iter, *args, **kwargs) - node.ifs = [self.visit(x, *args, **kwargs) for x in node.ifs] - return node - - def visitGenExprIf(self, node, *args, **kwargs): - node.test = self.visit(node.test, locals_=True, *args, **kwargs) - return node - - def visitGenExprInner(self, node, *args, **kwargs): - node.expr = self.visit(node.expr, locals_=True, *args, **kwargs) - node.quals = [self.visit(x, *args, **kwargs) for x in node.quals] - return node - def visitSlice(self, node, *args, **kwargs): node.expr = self.visit(node.expr, locals_=True, *args, **kwargs) if node.lower is not None: @@ -359,6 +356,10 @@ node.nodes = [self.visit(x, *args, **kwargs) for x in node.nodes] return node + def visitTuple(self, node, *args, **kwargs): + node.nodes = [self.visit(n, *args, **kwargs) for n in node.nodes] + return node + class ExpressionASTTransformer(ASTTransformer): """Concrete AST transformer that implements the AST transformations needed @@ -370,6 +371,15 @@ return ast.Const(node.value.decode('utf-8')) return node + def visitGenExprIf(self, node, *args, **kwargs): + node.test = self.visit(node.test, locals_=True) + return node + + def visitGenExprInner(self, node, *args, **kwargs): + node.expr = self.visit(node.expr, locals_=True) + node.quals = [self.visit(x) for x in node.quals] + return node + def visitGetattr(self, node, locals_=False): return ast.CallFunc(ast.Name('_lookup_attr'), [ ast.Name('data'), self.visit(node.expr, locals_=locals_), diff --git a/genshi/plugin.py b/genshi/plugin.py --- a/genshi/plugin.py +++ b/genshi/plugin.py @@ -21,7 +21,8 @@ from genshi.core import Attrs, Stream, QName from genshi.eval import Undefined from genshi.input import HTML, XML -from genshi.template import Context, MarkupTemplate, Template, TemplateLoader +from genshi.template import Context, MarkupTemplate, Template, TemplateLoader, \ + TextTemplate def ET(element): """Converts the given ElementTree element to a markup stream.""" @@ -42,9 +43,12 @@ yield Stream.TEXT, element.tail, (None, -1, -1) -class TemplateEnginePlugin(object): +class AbstractTemplateEnginePlugin(object): """Implementation of the plugin API.""" + template_class = None + extension = None + def __init__(self, extra_vars_func=None, options=None): if options is None: options = {} @@ -59,15 +63,15 @@ a string. """ if template_string is not None: - return MarkupTemplate(template_string) + return self.template_class(template_string) divider = templatename.rfind('.') if divider >= 0: package = templatename[:divider] - basename = templatename[divider + 1:] + '.html' + basename = templatename[divider + 1:] + self.extension templatename = resource_filename(package, basename) - return self.loader.load(templatename) + return self.loader.load(templatename, cls=self.template_class) def render(self, info, format='html', fragment=False, template=None): """Render the template to a string using the provided info.""" @@ -77,12 +81,7 @@ """Render the output to an event stream.""" if not isinstance(template, Template): template = self.load_template(template) - - data = {'ET': ET, 'HTML': HTML, 'XML': XML} - if self.get_extra_vars: - data.update(self.get_extra_vars()) - data.update(info) - ctxt = Context(**data) + ctxt = Context(**info) # Some functions for Kid compatibility def defined(name): @@ -93,3 +92,33 @@ ctxt['value_of'] = value_of return template.generate(ctxt) + + +class MarkupTemplateEnginePlugin(AbstractTemplateEnginePlugin): + """Implementation of the plugin API for markup templates.""" + + template_class = MarkupTemplate + extension = '.html' + + def transform(self, info, template): + """Render the output to an event stream.""" + data = {'ET': ET, 'HTML': HTML, 'XML': XML} + if self.get_extra_vars: + data.update(self.get_extra_vars()) + data.update(info) + return super(MarkupTemplateEnginePlugin, self).transform(data, template) + + +class TextTemplateEnginePlugin(AbstractTemplateEnginePlugin): + """Implementation of the plugin API for text templates.""" + + template_class = TextTemplate + extension = '.txt' + + def transform(self, info, template): + """Render the output to an event stream.""" + data = {} + if self.get_extra_vars: + data.update(self.get_extra_vars()) + data.update(info) + return super(TextTemplateEnginePlugin, self).transform(data, template) diff --git a/genshi/template.py b/genshi/template.py --- a/genshi/template.py +++ b/genshi/template.py @@ -31,9 +31,9 @@ from genshi.input import XMLParser from genshi.path import Path -__all__ = ['BadDirectiveError', 'TemplateError', 'TemplateSyntaxError', - 'TemplateNotFound', 'MarkupTemplate', 'TextTemplate', - 'TemplateLoader'] +__all__ = ['BadDirectiveError', 'MarkupTemplate', 'Template', 'TemplateError', + 'TemplateSyntaxError', 'TemplateNotFound', 'TemplateLoader', + 'TextTemplate'] class TemplateError(Exception): @@ -108,7 +108,7 @@ self._match_templates = [] def __repr__(self): - return repr(self.frames) + return repr(list(self.frames)) def __setitem__(self, key, value): """Set a variable in the current scope.""" @@ -384,6 +384,7 @@ # Store the function reference in the bottom context frame so that it # doesn't get popped off before processing the template has finished + # FIXME: this makes context data mutable as a side-effect ctxt.frames[-1][self.name] = function return [] @@ -433,8 +434,7 @@ ctxt.pop() def __repr__(self): - return '<%s "%s in %s">' % (self.__class__.__name__, - ', '.join(self.targets), self.expr.source) + return '<%s>' % self.__class__.__name__ class IfDirective(Directive): @@ -726,9 +726,7 @@ ctxt.pop() def __repr__(self): - return '<%s "%s">' % (self.__class__.__name__, - '; '.join(['%s = %s' % (name, expr.source) - for name, expr in self.vars])) + return '<%s>' % (self.__class__.__name__) class TemplateMeta(type): @@ -764,7 +762,7 @@ if basedir and filename: self.filepath = os.path.join(basedir, filename) else: - self.filepath = None + self.filepath = filename self.filters = [self._flatten, self._eval] @@ -967,7 +965,7 @@ ns_prefix = {} depth = 0 - for kind, data, pos in XMLParser(self.source, filename=self.filename): + for kind, data, pos in XMLParser(self.source, filename=self.filepath): if kind is START_NS: # Strip out the namespace declaration for template directives @@ -1173,7 +1171,7 @@ start, end = mo.span() if start > offset: text = source[offset:start] - for kind, data, pos in self._interpolate(text, self.filename, + for kind, data, pos in self._interpolate(text, self.filepath, lineno, 0): stream.append((kind, data, pos)) lineno += len(text.splitlines()) @@ -1192,12 +1190,12 @@ directive, start_offset = dirmap.pop(depth) substream = stream[start_offset:] stream[start_offset:] = [(SUB, ([directive], substream), - (self.filename, lineno, 0))] + (self.filepath, lineno, 0))] elif command != '#': cls = self._dir_by_name.get(command) if cls is None: raise BadDirectiveError(command) - directive = cls(value, self.filename, lineno, 0) + directive = cls(value, self.filepath, lineno, 0) dirmap[depth] = (directive, len(stream)) depth += 1 @@ -1205,7 +1203,7 @@ if offset < len(source): text = source[offset:].replace('\\#', '#') - for kind, data, pos in self._interpolate(text, self.filename, + for kind, data, pos in self._interpolate(text, self.filepath, lineno, 0): stream.append((kind, data, pos)) diff --git a/genshi/tests/eval.py b/genshi/tests/eval.py --- a/genshi/tests/eval.py +++ b/genshi/tests/eval.py @@ -9,7 +9,7 @@ # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision -# history and logs, available at hhttp://genshi.edgewall.org/log/. +# history and logs, available at http://genshi.edgewall.org/log/. import doctest import sys diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -52,6 +52,8 @@ extras_require = {'plugin': ['setuptools>=0.6a2']}, entry_points = """ [python.templating.engines] - genshi = genshi.plugin:TemplateEnginePlugin[plugin] + genshi = genshi.plugin:MarkupTemplateEnginePlugin[plugin] + genshi-markup = genshi.plugin:MarkupTemplateEnginePlugin[plugin] + genshi-text = genshi.plugin:TextTemplateEnginePlugin[plugin] """, )