changeset 272:186d18554183 stable-0.3.x 0.3.4

Ported [330],[333], and [334] to 0.3.x stable branch.
author cmlenz
date Wed, 27 Sep 2006 09:27:17 +0000
parents 22842c0ca176
children 65a46e008098
files examples/turbogears/genshitest/controllers.py examples/turbogears/genshitest/templates/plain.txt examples/turbogears/genshitest/templates/welcome.html genshi/eval.py genshi/plugin.py genshi/template.py genshi/tests/eval.py setup.py
diffstat 8 files changed, 109 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- 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):
 
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
--- a/examples/turbogears/genshitest/templates/welcome.html
+++ b/examples/turbogears/genshitest/templates/welcome.html
@@ -33,5 +33,7 @@
 
     ${ET(widget.display())}
 
+    <p>Here's a <a href="plain">link</a> to the output of a plain-text template.</p>
+
 </body>
 </html>
--- 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 = '<string>' # 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 = '<string>' # 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_),
--- 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)
--- 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))
 
--- 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
--- 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]
     """,
 )
Copyright (C) 2012-2017 Edgewall Software