some documentation improvements, jinja escapes " and ' now, both into charpoints...
[jinja2.git] / jinja2 / environment.py
index 33e8f76f034d6fb3a6f8792dce8a3246c0b2f436..35bbb15fe6daf1d68ad4c12bbab56bc47f16201e 100644 (file)
 
     Provides a class that holds runtime and parsing time options.
 
-    :copyright: 2007 by Armin Ronacher.
+    :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+import sys
+from jinja2.defaults import *
 from jinja2.lexer import Lexer
 from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
-from jinja2.runtime import Undefined
-from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
+from jinja2.runtime import Undefined, Context
+from jinja2.debug import translate_exception, translate_syntax_error
+from jinja2.exceptions import TemplateSyntaxError
+from jinja2.utils import import_string, LRUCache, Markup, missing, concat
 
 
-class Environment(object):
-    """The Jinja environment.
+# for direct template usage we have up to ten living environments
+_spontaneous_environments = LRUCache(10)
+
+
+def get_spontaneous_environment(*args):
+    """Return a new spontaneus environment.  A spontaneus environment is an
+    unnamed and unaccessable (in theory) environment that is used for
+    template generated from a string and not from the file system.
+    """
+    try:
+        env = _spontaneous_environments.get(args)
+    except TypeError:
+        return Environment(*args)
+    if env is not None:
+        return env
+    _spontaneous_environments[args] = env = Environment(*args)
+    env.shared = True
+    return env
+
+
+def create_cache(size):
+    """Return the cache class for the given size."""
+    if size == 0:
+        return None
+    if size < 0:
+        return {}
+    return LRUCache(size)
+
+
+def load_extensions(environment, extensions):
+    """Load the extensions from the list and bind it to the environment.
+    Returns a new list of instanciated environments.
+    """
+    result = []
+    for extension in extensions:
+        if isinstance(extension, basestring):
+            extension = import_string(extension)
+        result.append(extension(environment))
+    return result
+
 
-    The core component of Jinja is the `Environment`. It contains
+def _environment_sanity_check(environment):
+    """Perform a sanity check on the environment."""
+    assert issubclass(environment.undefined, Undefined), 'undefined must ' \
+           'be a subclass of undefined because filters depend on it.'
+    assert environment.block_start_string != \
+           environment.variable_start_string != \
+           environment.comment_start_string, 'block, variable and comment ' \
+           'start strings must be different'
+    return environment
+
+
+class Environment(object):
+    """The core component of Jinja is the `Environment`.  It contains
     important shared variables like configuration, filters, tests,
-    globals and others.
+    globals and others.  Instances of this class may be modified if
+    they are not shared and if no template was loaded so far.
+    Modifications on environments after the first template was loaded
+    will lead to surprising effects and undefined behavior.
+
+    Here the possible initialization parameters:
+
+    `block_start_string`
+        The string marking the begin of a block.  Defaults to ``'{%'``.
+
+    `block_end_string`
+        The string marking the end of a block.  Defaults to ``'%}'``.
+
+    `variable_start_string`
+        The string marking the begin of a print statement.
+        Defaults to ``'{{'``.
+
+    `variable_stop_string`
+        The string marking the end of a print statement. Defaults to ``'}}'``.
+
+    `comment_start_string`
+        The string marking the begin of a comment.  Defaults to ``'{#'``.
+
+    `comment_end_string`
+        The string marking the end of a comment.  Defaults to ``'#}'``.
+
+    `line_statement_prefix`
+        If given and a string, this will be used as prefix for line based
+        statements.  See also :ref:`line-statements`.
+
+    `trim_blocks`
+        If this is set to ``True`` the first newline after a block is
+        removed (block, not variable tag!).  Defaults to `False`.
+
+    `extensions`
+        List of Jinja extensions to use.  This can either be import paths
+        as strings or extension classes.
+
+    `optimized`
+        should the optimizer be enabled?  Default is `True`.
+
+    `undefined`
+        :class:`Undefined` or a subclass of it that is used to represent
+        undefined values in the template.
+
+    `finalize`
+        A callable that finalizes the variable.  Per default no finalizing
+        is applied.
+
+    `autoescape`
+        If set to true the XML/HTML autoescaping feature is enabled.
+
+    `loader`
+        The template loader for this environment.
+
+    `cache_size`
+        The size of the cache.  Per default this is ``50`` which means that if
+        more than 50 templates are loaded the loader will clean out the least
+        recently used template.  If the cache size is set to ``0`` templates are
+        recompiled all the time, if the cache size is ``-1`` the cache will not
+        be cleaned.
+
+    `auto_reload`
+        Some loaders load templates from locations where the template sources
+        may change (ie: file system or database).  If `auto_reload` is set to
+        `True` (default) every time a template is requested the loader checks
+        if the source changed and if yes, it will reload the template.  For
+        higher performance it's possible to disable that.
     """
 
     #: if this environment is sandboxed.  Modifying this variable won't make
@@ -29,46 +150,43 @@ class Environment(object):
     #: have a look at jinja2.sandbox
     sandboxed = False
 
+    #: True if the environment is just an overlay
+    overlay = False
+
+    #: the environment this environment is linked to if it is an overlay
+    linked_to = None
+
+    #: shared environments have this set to `True`.  A shared environment
+    #: must not be modified
+    shared = False
+
     def __init__(self,
-                 block_start_string='{%',
-                 block_end_string='%}',
-                 variable_start_string='{{',
-                 variable_end_string='}}',
-                 comment_start_string='{#',
-                 comment_end_string='#}',
-                 line_statement_prefix=None,
+                 block_start_string=BLOCK_START_STRING,
+                 block_end_string=BLOCK_END_STRING,
+                 variable_start_string=VARIABLE_START_STRING,
+                 variable_end_string=VARIABLE_END_STRING,
+                 comment_start_string=COMMENT_START_STRING,
+                 comment_end_string=COMMENT_END_STRING,
+                 line_statement_prefix=LINE_STATEMENT_PREFIX,
                  trim_blocks=False,
+                 extensions=(),
                  optimized=True,
                  undefined=Undefined,
-                 loader=None):
-        """Here the possible initialization parameters:
-
-        ========================= ============================================
-        `block_start_string`      the string marking the begin of a block.
-                                  this defaults to ``'{%'``.
-        `block_end_string`        the string marking the end of a block.
-                                  defaults to ``'%}'``.
-        `variable_start_string`   the string marking the begin of a print
-                                  statement. defaults to ``'{{'``.
-        `comment_start_string`    the string marking the begin of a
-                                  comment. defaults to ``'{#'``.
-        `comment_end_string`      the string marking the end of a comment.
-                                  defaults to ``'#}'``.
-        `line_statement_prefix`   If given and a string, this will be used as
-                                  prefix for line based statements.  See the
-                                  documentation for more details.
-        `trim_blocks`             If this is set to ``True`` the first newline
-                                  after a block is removed (block, not
-                                  variable tag!). Defaults to ``False``.
-        `optimized`               should the optimizer be enabled?  Default is
-                                  ``True``.
-        `undefined`               a subclass of `Undefined` that is used to
-                                  represent undefined variables.
-        `loader`                  the loader which should be used.
-        ========================= ============================================
-        """
-        assert issubclass(undefined, Undefined), 'undefined must be ' \
-               'a subclass of undefined because filters depend on it.'
+                 finalize=None,
+                 autoescape=False,
+                 loader=None,
+                 cache_size=50,
+                 auto_reload=True):
+        # !!Important notice!!
+        #   The constructor accepts quite a few arguments that should be
+        #   passed by keyword rather than position.  However it's important to
+        #   not change the order of arguments because it's used at least
+        #   internally in those cases:
+        #       -   spontaneus environments (i18n extension and Template)
+        #       -   unittests
+        #   If parameter changes are required only add parameters at the end
+        #   and don't change the arguments (or the defaults!) of the arguments
+        #   existing already.
 
         # lexer / parser information
         self.block_start_string = block_start_string
@@ -79,25 +197,73 @@ class Environment(object):
         self.comment_end_string = comment_end_string
         self.line_statement_prefix = line_statement_prefix
         self.trim_blocks = trim_blocks
+
+        # runtime information
         self.undefined = undefined
         self.optimized = optimized
+        self.finalize = finalize
+        self.autoescape = autoescape
 
         # defaults
         self.filters = DEFAULT_FILTERS.copy()
         self.tests = DEFAULT_TESTS.copy()
         self.globals = DEFAULT_NAMESPACE.copy()
 
-        # if no finalize function/method exists we default to unicode.  The
-        # compiler check if the finalize attribute *is* unicode, if yes no
-        # finalizaion is written where it can be avoided.
-        if not hasattr(self, 'finalize'):
-            self.finalize = unicode
-
         # set the loader provided
         self.loader = loader
+        self.cache = create_cache(cache_size)
+        self.auto_reload = auto_reload
+
+        # load extensions
+        self.extensions = load_extensions(self, extensions)
+
+        _environment_sanity_check(self)
+
+    def overlay(self, block_start_string=missing, block_end_string=missing,
+                variable_start_string=missing, variable_end_string=missing,
+                comment_start_string=missing, comment_end_string=missing,
+                line_statement_prefix=missing, trim_blocks=missing,
+                extensions=missing, optimized=missing, undefined=missing,
+                finalize=missing, autoescape=missing, loader=missing,
+                cache_size=missing, auto_reload=missing):
+        """Create a new overlay environment that shares all the data with the
+        current environment except of cache and the overriden attributes.
+        Extensions cannot be removed for a overlayed environment.  A overlayed
+        environment automatically gets all the extensions of the environment it
+        is linked to plus optional extra extensions.
+
+        Creating overlays should happen after the initial environment was set
+        up completely.  Not all attributes are truly linked, some are just
+        copied over so modifications on the original environment may not shine
+        through.
+        """
+        args = dict(locals())
+        del args['self'], args['cache_size'], args['extensions']
 
-        # create lexer
-        self.lexer = Lexer(self)
+        rv = object.__new__(self.__class__)
+        rv.__dict__.update(self.__dict__)
+        rv.overlay = True
+        rv.linked_to = self
+
+        for key, value in args.iteritems():
+            if value is not missing:
+                setattr(rv, key, value)
+
+        if cache_size is not missing:
+            rv.cache = create_cache(cache_size)
+
+        rv.extensions = []
+        for extension in self.extensions:
+            rv.extensions.append(extension.bind(self))
+        if extensions is not missing:
+            rv.extensions.extend(load_extensions(extensions))
+
+        return _environment_sanity_check(rv)
+
+    @property
+    def lexer(self):
+        """Return a fresh lexer for the environment."""
+        return Lexer(self)
 
     def subscribe(self, obj, argument):
         """Get an item or attribute of an object."""
@@ -107,58 +273,105 @@ class Environment(object):
             try:
                 return obj[argument]
             except (TypeError, LookupError):
-                return self.undefined(objargument)
+                return self.undefined(obj=obj, name=argument)
 
     def parse(self, source, filename=None):
-        """Parse the sourcecode and return the abstract syntax tree. This tree
-        of nodes is used by the compiler to convert the template into
-        executable source- or bytecode.
+        """Parse the sourcecode and return the abstract syntax tree.  This
+        tree of nodes is used by the compiler to convert the template into
+        executable source- or bytecode.  This is useful for debugging or to
+        extract information from templates.
         """
-        parser = Parser(self, source, filename)
-        return parser.parse()
-
-    def lex(self, source, filename=None):
-        """Lex the given sourcecode and return a generator that yields tokens.
-        The stream returned is not usable for Jinja but can be used if
-        Jinja templates should be processed by other tools (for example
-        syntax highlighting etc)
-
-        The tuples are returned in the form ``(lineno, token, value)``.
+        try:
+            return Parser(self, source, filename).parse()
+        except TemplateSyntaxError, e:
+            exc_type, exc_value, tb = translate_syntax_error(e)
+            raise exc_type, exc_value, tb
+
+    def lex(self, source, name=None):
+        """Lex the given sourcecode and return a generator that yields
+        tokens as tuples in the form ``(lineno, token_type, value)``.
+        """
+        return self.lexer.tokeniter(source, name)
+
+    def compile(self, source, name=None, filename=None, globals=None,
+                raw=False):
+        """Compile a node or template source code.  The `name` parameter is
+        the load name of the template after it was joined using
+        :meth:`join_path` if necessary, not the filename on the file system.
+        the `filename` parameter is the estimated filename of the template on
+        the file system.  If the template came from a database or memory this
+        can be omitted.  The `globals` parameter can be used to provide extra
+        variables at compile time for the template.  In the future the
+        optimizer will be able to evaluate parts of the template at compile
+        time based on those variables.
+
+        The return value of this method is a python code object.  If the `raw`
+        parameter is `True` the return value will be a string with python
+        code equivalent to the bytecode returned otherwise.  This method is
+        mainly used internally.
         """
-        return self.lexer.tokeniter(source, filename)
-
-    def compile(self, source, filename=None, raw=False, globals=None):
-        """Compile a node or source."""
         if isinstance(source, basestring):
             source = self.parse(source, filename)
         if self.optimized:
             node = optimize(source, self, globals or {})
-        source = generate(node, self, filename)
+        source = generate(node, self, name, filename)
         if raw:
             return source
-        if isinstance(filename, unicode):
+        if filename is None:
+            filename = '<template>'
+        elif isinstance(filename, unicode):
             filename = filename.encode('utf-8')
         return compile(source, filename, 'exec')
 
     def join_path(self, template, parent):
         """Join a template with the parent.  By default all the lookups are
-        relative to the loader root, but if the paths should be relative this
-        function can be used to calculate the real filename."""
+        relative to the loader root so this method returns the `template`
+        parameter unchanged, but if the paths should be relative to the
+        parent template, this function can be used to calculate the real
+        template name.
+
+        Subclasses may override this method and implement template path
+        joining here.
+        """
         return template
 
     def get_template(self, name, parent=None, globals=None):
-        """Load a template."""
+        """Load a template from the loader.  If a loader is configured this
+        method ask the loader for the template and returns a :class:`Template`.
+        If the `parent` parameter is not `None`, :meth:`join_path` is called
+        to get the real template name before loading.
+
+        The `globals` parameter can be used to provide compile-time globals.
+        In the future this will allow the optimizer to render parts of the
+        templates at compile-time.
+
+        If the template does not exist a :exc:`TemplateNotFound` exception is
+        raised.
+        """
         if self.loader is None:
             raise TypeError('no loader for this environment specified')
         if parent is not None:
             name = self.join_path(name, parent)
-        globals = self.make_globals(globals)
-        return self.loader.load(self, name, globals)
 
-    def from_string(self, source, filename='<string>', globals=None):
-        """Load a template from a string."""
+        if self.cache is not None:
+            template = self.cache.get(name)
+            if template is not None and (not self.auto_reload or \
+                                         template.is_up_to_date):
+                return template
+
+        template = self.loader.load(self, name, self.make_globals(globals))
+        if self.cache is not None:
+            self.cache[name] = template
+        return template
+
+    def from_string(self, source, globals=None, template_class=None):
+        """Load a template from a string.  This parses the source given and
+        returns a :class:`Template` object.
+        """
         globals = self.make_globals(globals)
-        return Template(self, self.compile(source, filename), globals)
+        cls = template_class or self.template_class
+        return cls.from_code(self, self.compile(source, globals=globals),
+                             globals, None)
 
     def make_globals(self, d):
         """Return a dict for the globals."""
@@ -168,35 +381,128 @@ class Environment(object):
 
 
 class Template(object):
-    """Represents a template."""
+    """The central template object.  This class represents a compiled template
+    and is used to evaluate it.
+
+    Normally the template object is generated from an :class:`Environment` but
+    it also has a constructor that makes it possible to create a template
+    instance directly using the constructor.  It takes the same arguments as
+    the environment constructor but it's not possible to specify a loader.
+
+    Every template object has a few methods and members that are guaranteed
+    to exist.  However it's important that a template object should be
+    considered immutable.  Modifications on the object are not supported.
+
+    Template objects created from the constructor rather than an environment
+    do have an `environment` attribute that points to a temporary environment
+    that is probably shared with other templates created with the constructor
+    and compatible settings.
+
+    >>> template = Template('Hello {{ name }}!')
+    >>> template.render(name='John Doe')
+    u'Hello John Doe!'
+
+    >>> stream = template.stream(name='John Doe')
+    >>> stream.next()
+    u'Hello John Doe!'
+    >>> stream.next()
+    Traceback (most recent call last):
+        ...
+    StopIteration
+    """
 
-    def __init__(self, environment, code, globals):
-        namespace = {'environment': environment}
+    def __new__(cls, source,
+                block_start_string='{%',
+                block_end_string='%}',
+                variable_start_string='{{',
+                variable_end_string='}}',
+                comment_start_string='{#',
+                comment_end_string='#}',
+                line_statement_prefix=None,
+                trim_blocks=False,
+                extensions=(),
+                optimized=True,
+                undefined=Undefined,
+                finalize=None,
+                autoescape=False):
+        env = get_spontaneous_environment(
+            block_start_string, block_end_string, variable_start_string,
+            variable_end_string, comment_start_string, comment_end_string,
+            line_statement_prefix, trim_blocks, tuple(extensions), optimized,
+            undefined, finalize, autoescape, None, 0, False)
+        return env.from_string(source, template_class=cls)
+
+    @classmethod
+    def from_code(cls, environment, code, globals, uptodate=None):
+        """Creates a template object from compiled code and the globals.  This
+        is used by the loaders and environment to create a template object.
+        """
+        t = object.__new__(cls)
+        namespace = {
+            'environment':          environment,
+            '__jinja_template__':   t
+        }
         exec code in namespace
-        self.environment = environment
-        self.name = namespace['filename']
-        self.root_render_func = namespace['root']
-        self.blocks = namespace['blocks']
-        self.globals = globals
+        t.environment = environment
+        t.name = namespace['name']
+        t.filename = code.co_filename
+        t.root_render_func = namespace['root']
+        t.blocks = namespace['blocks']
+        t.globals = globals
+
+        # debug and loader helpers
+        t._debug_info = namespace['debug_info']
+        t._uptodate = uptodate
+
+        return t
 
     def render(self, *args, **kwargs):
-        return u''.join(self.generate(*args, **kwargs))
+        """This method accepts the same arguments as the `dict` constructor:
+        A dict, a dict subclass or some keyword arguments.  If no arguments
+        are given the context will be empty.  These two calls do the same::
+
+            template.render(knights='that say nih')
+            template.render({'knights': 'that say nih'})
+
+        This will return the rendered template as unicode string.
+        """
+        try:
+            return concat(self._generate(*args, **kwargs))
+        except:
+            exc_type, exc_value, tb = translate_exception(sys.exc_info())
+            raise exc_type, exc_value, tb
 
     def stream(self, *args, **kwargs):
+        """Works exactly like :meth:`generate` but returns a
+        :class:`TemplateStream`.
+        """
         return TemplateStream(self.generate(*args, **kwargs))
 
     def generate(self, *args, **kwargs):
+        """For very large templates it can be useful to not render the whole
+        template at once but evaluate each statement after another and yield
+        piece for piece.  This method basically does exactly that and returns
+        a generator that yields one item after another as unicode strings.
+
+        It accepts the same arguments as :meth:`render`.
+        """
+        try:
+            for item in self._generate(*args, **kwargs):
+                yield item
+        except:
+            exc_type, exc_value, tb = translate_exception(sys.exc_info())
+            raise exc_type, exc_value, tb
+
+    def _generate(self, *args, **kwargs):
         # assemble the context
-        local_context = dict(*args, **kwargs)
-        context = self.globals.copy()
-        context.update(local_context)
+        context = dict(*args, **kwargs)
 
         # if the environment is using the optimizer locals may never
         # override globals as optimizations might have happened
         # depending on values of certain globals.  This assertion goes
         # away if the python interpreter is started with -O
         if __debug__ and self.environment.optimized:
-            overrides = set(local_context) & set(self.globals)
+            overrides = set(context) & set(self.globals)
             if overrides:
                 plural = len(overrides) != 1 and 's' or ''
                 raise AssertionError('the per template variable%s %s '
@@ -204,20 +510,120 @@ class Template(object):
                                      'With an enabled optimizer this '
                                      'will lead to unexpected results.' %
                     (plural, ', '.join(overrides), plural or ' a', plural))
-        gen = self.root_render_func(context)
-        # skip the first item which is a reference to the stream
-        gen.next()
-        return gen
+
+        return self.root_render_func(self.new_context(context))
+
+    def new_context(self, vars=None, shared=False):
+        """Create a new template context for this template.  The vars
+        provided will be passed to the template.  Per default the globals
+        are added to the context, if shared is set to `True` the data
+        provided is used as parent namespace.  This is used to share the
+        same globals in multiple contexts without consuming more memory.
+        (This works because the context does not modify the parent dict)
+        """
+        if vars is None:
+            vars = {}
+        if shared:
+            parent = vars
+        else:
+            parent = dict(self.globals, **vars)
+        return Context(self.environment, parent, self.name, self.blocks)
+
+    def make_module(self, vars=None, shared=False):
+        """This method works like the :attr:`module` attribute when called
+        without arguments but it will evaluate the template every call
+        rather then caching the template.  It's also possible to provide
+        a dict which is then used as context.  The arguments are the same
+        as fo the :meth:`new_context` method.
+        """
+        return TemplateModule(self, self.new_context(vars, shared))
+
+    @property
+    def module(self):
+        """The template as module.  This is used for imports in the
+        template runtime but is also useful if one wants to access
+        exported template variables from the Python layer:
+
+        >>> t = Template('{% macro foo() %}42{% endmacro %}23')
+        >>> unicode(t.module)
+        u'23'
+        >>> t.module.foo()
+        u'42'
+        """
+        if hasattr(self, '_module'):
+            return self._module
+        self._module = rv = self.make_module()
+        return rv
+
+    def get_corresponding_lineno(self, lineno):
+        """Return the source line number of a line number in the
+        generated bytecode as they are not in sync.
+        """
+        for template_line, code_line in reversed(self.debug_info):
+            if code_line <= lineno:
+                return template_line
+        return 1
+
+    @property
+    def is_up_to_date(self):
+        """If this variable is `False` there is a newer version available."""
+        if self._uptodate is None:
+            return True
+        return self._uptodate()
+
+    @property
+    def debug_info(self):
+        """The debug info mapping."""
+        return [tuple(map(int, x.split('='))) for x in
+                self._debug_info.split('&')]
 
     def __repr__(self):
-        return '<%s %r>' % (
-            self.__class__.__name__,
-            self.name
-        )
+        if self.name is None:
+            name = 'memory:%x' % id(self)
+        else:
+            name = repr(self.name)
+        return '<%s %s>' % (self.__class__.__name__, name)
+
+
+class TemplateModule(object):
+    """Represents an imported template.  All the exported names of the
+    template are available as attributes on this object.  Additionally
+    converting it into an unicode- or bytestrings renders the contents.
+    """
+
+    def __init__(self, template, context):
+        # don't alter this attribute unless you change it in the
+        # compiler too.  The Include without context passing directly
+        # uses the mangled name.  The reason why we use a mangled one
+        # is to avoid name clashes with macros with those names.
+        self.__body_stream = list(template.root_render_func(context))
+        self.__dict__.update(context.get_exported())
+        self.__name__ = template.name
+
+    __html__ = lambda x: Markup(concat(x.__body_stream))
+    __unicode__ = lambda x: unicode(concat(x.__body_stream))
+
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+
+    def __repr__(self):
+        if self.__name__ is None:
+            name = 'memory:%x' % id(self)
+        else:
+            name = repr(self.name)
+        return '<%s %s>' % (self.__class__.__name__, name)
 
 
 class TemplateStream(object):
-    """Wraps a genererator for outputing template streams."""
+    """A template stream works pretty much like an ordinary python generator
+    but it can buffer multiple items to reduce the number of total iterations.
+    Per default the output is unbuffered which means that for every unbuffered
+    instruction in the template one unicode string is yielded.
+
+    If buffering is enabled with a buffer size of 5, five items are combined
+    into a new unicode string.  This is mainly useful if you are streaming
+    big templates to a client via WSGI which flushes after each iteration.
+    """
 
     def __init__(self, gen):
         self._gen = gen
@@ -230,34 +636,38 @@ class TemplateStream(object):
         self.buffered = False
 
     def enable_buffering(self, size=5):
-        """Enable buffering. Buffer `size` items before yielding them."""
+        """Enable buffering.  Buffer `size` items before yielding them."""
         if size <= 1:
             raise ValueError('buffer size too small')
-        self.buffered = True
 
-        def buffering_next():
+        def generator():
             buf = []
             c_size = 0
             push = buf.append
             next = self._gen.next
 
-            try:
-                while 1:
-                    item = next()
-                    if item:
-                        push(item)
+            while 1:
+                try:
+                    while c_size < size:
+                        push(next())
                         c_size += 1
-                    if c_size >= size:
-                        raise StopIteration()
-            except StopIteration:
-                if not c_size:
-                    raise
-            return u''.join(buf)
+                except StopIteration:
+                    if not c_size:
+                        return
+                yield concat(buf)
+                del buf[:]
+                c_size = 0
 
-        self._next = buffering_next
+        self.buffered = True
+        self._next = generator().next
 
     def __iter__(self):
         return self
 
     def next(self):
         return self._next()
+
+
+# hook in default template class.  if anyone reads this comment: ignore that
+# it's possible to use custom templates ;-)
+Environment.template_class = Template