autoescaping is separate from finalize now and Markup is completely ignored if the...
authorArmin Ronacher <armin.ronacher@active-4.com>
Mon, 28 Apr 2008 10:20:12 +0000 (12:20 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Mon, 28 Apr 2008 10:20:12 +0000 (12:20 +0200)
--HG--
branch : trunk

docs/conf.py
docs/index.rst
jinja2/__init__.py
jinja2/compiler.py
jinja2/environment.py
jinja2/exceptions.py
jinja2/ext.py
jinja2/filters.py
jinja2/loaders.py
jinja2/runtime.py

index b81ae0ad7722fec882ce716f3baba52acbba2ecd..501c094489a65fbea59e7474acb25bb80236ff1a 100644 (file)
@@ -23,7 +23,7 @@ import sys, os
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-#extensions = []
+extensions = ['sphinx.ext.autodoc']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
@@ -67,7 +67,7 @@ today_fmt = '%B %d, %Y'
 #show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'autumn'
 
 
 # Options for HTML output
@@ -120,7 +120,7 @@ htmlhelp_basename = 'Jinja2doc'
 # ------------------------
 
 # The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
+latex_paper_size = 'a4'
 
 # The font size ('10pt', '11pt' or '12pt').
 #latex_font_size = '10pt'
@@ -132,7 +132,7 @@ latex_documents = [
 ]
 
 # Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
+latex_preamble = ''
 
 # Documents to append as an appendix to all manuals.
 #latex_appendices = []
index 34458f2718b2a61b69431b2f046ea50af36a894a..c548b91c6cb0cf7655747938cbc692f2c8d4f4d3 100644 (file)
@@ -1,7 +1,3 @@
-.. Jinja2 documentation master file, created by sphinx-quickstart on Sun Apr 27 21:42:41 2008.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
-
 Welcome to Jinja2's documentation!
 ==================================
 
@@ -10,10 +6,13 @@ Contents:
 .. toctree::
    :maxdepth: 2
 
+   intro
+   api
+   templates
+
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-
index 8141aadfa7ccccb925cba32ba206f2a6ffb6e825..4310d34e01916c4ea4abfa2cfcd71643e28753bf 100644 (file)
@@ -43,6 +43,10 @@ from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
 # undefined types
 from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
 
+# exceptions
+from jinja2.exceptions import TemplateError, UndefinedError, \
+     TemplateNotFound, TemplateSyntaxError, TemplateAssertionError
+
 # decorators and public utilities
 from jinja2.filters import environmentfilter, contextfilter
 from jinja2.utils import Markup, escape, environmentfunction, contextfunction
index 8c699f5d5bafcd31642d0c62477f99fbdc9e3ed5..2631c450440eb74fbc42f1a821095342a308a50d 100644 (file)
@@ -843,7 +843,10 @@ class CodeGenerator(NodeVisitor):
         self.pull_locals(macro_frame)
         self.writeline('%s = []' % buf)
         self.blockvisit(node.body, macro_frame)
-        self.writeline("return Markup(concat(%s))" % buf)
+        if self.environment.autoescape:
+            self.writeline('return Markup(concat(%s))' % buf)
+        else:
+            self.writeline("return concat(%s)" % buf)
         self.outdent()
         self.newline()
         if frame.toplevel:
@@ -874,7 +877,10 @@ class CodeGenerator(NodeVisitor):
         self.pull_locals(call_frame)
         self.writeline('%s = []' % buf)
         self.blockvisit(node.body, call_frame)
-        self.writeline("return Markup(concat(%s))" % buf)
+        if self.environment.autoescape:
+            self.writeline("return Markup(concat(%s))" % buf)
+        else:
+            self.writeline('return concat(%s)' % buf)
         self.outdent()
         arg_tuple = ', '.join(repr(x.name) for x in node.args)
         if len(node.args) == 1:
@@ -927,12 +933,6 @@ class CodeGenerator(NodeVisitor):
             return
 
         self.newline(node)
-        if self.environment.finalize is unicode:
-            finalizer = 'unicode'
-            have_finalizer = False
-        else:
-            finalizer = 'environment.finalize'
-            have_finalizer = True
 
         # if we are in the toplevel scope and there was already an extends
         # statement we have to add a check that disables our yield(s) here
@@ -972,9 +972,16 @@ class CodeGenerator(NodeVisitor):
                 else:
                     if frame.buffer is None:
                         self.writeline('yield ')
-                    self.write(finalizer + '(')
+                    close = 1
+                    if self.environment.autoescape:
+                        self.write('escape(')
+                    else:
+                        self.write('unicode(')
+                    if self.environment.finalize is not None:
+                        self.write('environment.finalize(')
+                        close += 1
                     self.visit(item, frame)
-                    self.write(')')
+                    self.write(')' * close)
                     if frame.buffer is not None:
                         self.write(', ')
             if frame.buffer is not None:
@@ -999,12 +1006,15 @@ class CodeGenerator(NodeVisitor):
             self.indent()
             for argument in arguments:
                 self.newline(argument)
-                if have_finalizer:
-                    self.write(finalizer + '(')
+                close = 0
+                if self.environment.autoescape:
+                    self.write('escape(')
+                    close += 1
+                if self.environment.finalize is not None:
+                    self.write('environment.finalize(')
+                    close += 1
                 self.visit(argument, frame)
-                if have_finalizer:
-                    self.write(')')
-                self.write(',')
+                self.write(')' * close + ',')
             self.outdent()
             self.writeline(')')
             if frame.buffer is not None:
@@ -1105,6 +1115,13 @@ class CodeGenerator(NodeVisitor):
     visit_Not = uaop('not ')
     del binop, uaop
 
+    def visit_Concat(self, node, frame):
+        self.write('join((')
+        for arg in node.nodes:
+            self.visit(arg, frame)
+            self.write(', ')
+        self.write('))')
+
     def visit_Compare(self, node, frame):
         self.visit(node.expr, frame)
         for op in node.ops:
index 6a00fda2c27dcd96790200f7686cc4274871406b..7f4144be014ef403fc5d37c782b0daac2dcb2af6 100644 (file)
@@ -65,11 +65,59 @@ def template_from_code(environment, code, globals, uptodate=None,
 
 
 class Environment(object):
-    """The Jinja environment.
-
-    The core component of Jinja is the `Environment`. It contains
+    """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 ``'{{'``.
+
+    `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.
+
+    `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.
     """
 
     #: if this environment is sandboxed.  Modifying this variable won't make
@@ -93,7 +141,8 @@ class Environment(object):
                  extensions=(),
                  optimized=True,
                  undefined=Undefined,
-                 finalize=unicode,
+                 finalize=None,
+                 autoescape=False,
                  loader=None):
         # !!Important notice!!
         #   The constructor accepts quite a few arguments that should be
@@ -105,36 +154,6 @@ class Environment(object):
         #   If parameter changes are required only add parameters at the end
         #   and don't change the arguments (or the defaults!) of the arguments
         #   up to (but excluding) loader.
-        """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``.
-        `extensions`              List of Jinja extensions to use.
-        `optimized`               should the optimizer be enabled?  Default is
-                                  ``True``.
-        `undefined`               a subclass of `Undefined` that is used to
-                                  represent undefined variables.
-        `finalize`                A callable that finalizes the variable.  Per
-                                  default this is `unicode`, other useful
-                                  builtin finalizers are `escape`.
-        `loader`                  the loader which should be used.
-        ========================= ============================================
-        """
 
         # santity checks
         assert issubclass(undefined, Undefined), 'undefined must be ' \
@@ -157,6 +176,7 @@ class Environment(object):
         self.undefined = undefined
         self.optimized = optimized
         self.finalize = finalize
+        self.autoescape = autoescape
 
         # defaults
         self.filters = DEFAULT_FILTERS.copy()
@@ -187,29 +207,35 @@ class Environment(object):
                 return self.undefined(obj=obj, name=argument)
 
     def parse(self, source, name=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.
         """
         return Parser(self, source, name).parse()
 
     def lex(self, source, name=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)``.
+        """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 source.  The name is the load name of the
-        template after it was joined using `join_path` if necessary,
-        filename is the estimated filename of the template on the file
-        system.  If the template came from a database or memory this
-        can be omitted.
+        """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.
         """
         if isinstance(source, basestring):
             source = self.parse(source, name)
@@ -226,12 +252,29 @@ class Environment(object):
 
     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:
@@ -239,7 +282,9 @@ class Environment(object):
         return self.loader.load(self, name, self.make_globals(globals))
 
     def from_string(self, source, globals=None, template_class=None):
-        """Load a template from a string."""
+        """Load a template from a string.  This parses the source given and
+        returns a :class:`Template` object.
+        """
         globals = self.make_globals(globals)
         return template_from_code(self, self.compile(source, globals=globals),
                                   globals, None, template_class)
@@ -255,8 +300,8 @@ class Template(object):
     """The central template object.  This class represents a compiled template
     and is used to evaluate it.
 
-    Normally the template object is generated from an `Environment` but it
-    also has a constructor that makes it possible to create a template
+    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.
 
@@ -294,16 +339,25 @@ class Template(object):
                 extensions=(),
                 optimized=True,
                 undefined=Undefined,
-                finalize=unicode):
+                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)
+            undefined, finalize, autoescape)
         return env.from_string(source, template_class=cls)
 
     def render(self, *args, **kwargs):
-        """Render the template into a string."""
+        """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:
@@ -312,7 +366,9 @@ class Template(object):
             raise exc_type, exc_value, tb.tb_next
 
     def stream(self, *args, **kwargs):
-        """Return a `TemplateStream` that generates the template."""
+        """Works exactly like :meth:`generate` but returns a
+        :class:`TemplateStream`.
+        """
         try:
             return TemplateStream(self.generate(*args, **kwargs))
         except:
@@ -321,7 +377,13 @@ class Template(object):
             raise exc_type, exc_value, tb.tb_next
 
     def generate(self, *args, **kwargs):
-        """Return a generator that generates the template."""
+        """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`.
+        """
         # assemble the context
         context = dict(*args, **kwargs)
 
@@ -364,11 +426,24 @@ class Template(object):
                                self.blocks)
 
     def include(self, vars=None):
-        """Include this template.  When passed a template context or dict
-        the template is evaluated in that context and an `IncludedTemplate`
-        object is returned.  This object then exposes all the exported
-        variables as attributes and renders the contents of the template
-        when converted to unicode.
+        """Some templates may export macros or other variables.  It's possible
+        to access those variables by "including" the template.  This is mainly
+        used internally but may also be useful on the Python layer.  If passed
+        a context, the template is evaluated in it, otherwise an empty context
+        with just the globals is used.
+
+        The return value is an included template object.  Converting it to
+        unicode returns the rendered contents of the template, the exported
+        variables are accessable via the attribute syntax.
+
+        This example shows how it can be used:
+
+        >>> t = Template('{% say_hello(name) %}Hello {{ name }}!{% endmacro %}42')
+        >>> i = t.include()
+        >>> unicode(i)
+        u'42'
+        >>> i.say_hello('John')
+        u'Hello John!'
         """
         if isinstance(vars, TemplateContext):
             context = TemplateContext(self.environment, vars.parent,
@@ -433,9 +508,14 @@ class IncludedTemplate(object):
 
 
 class TemplateStream(object):
-    """This class wraps a generator returned from `Template.generate` so that
-    it's possible to buffer multiple elements so that it's possible to return
-    them from a WSGI application which flushes after each iteration.
+    """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):
@@ -449,7 +529,7 @@ 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')
 
index b742959726bff513c865b10e7633dd6370dc9ea1..0bbe33e0d04f34c5112201d62b30b6dc2d7c8d9c 100644 (file)
@@ -5,7 +5,7 @@
 
     Jinja exceptions.
 
-    :copyright: 2007 by Armin Ronacher.
+    :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
 
@@ -15,7 +15,7 @@ class TemplateError(Exception):
 
 
 class UndefinedError(TemplateError):
-    """Raised if a template tries to operate on `Undefined`."""
+    """Raised if a template tries to operate on :class:`Undefined`."""
 
 
 class TemplateNotFound(IOError, LookupError, TemplateError):
@@ -44,6 +44,4 @@ class TemplateAssertionError(TemplateSyntaxError):
 
 
 class TemplateRuntimeError(TemplateError):
-    """Raised by the template engine if a tag encountered an error when
-    rendering.
-    """
+    """A runtime error."""
index e480b00c2e83a1ecb165081fd426a111b9bbedfb..5ae317d0ea1fe3674ff5e939b7640c98ffce1326 100644 (file)
@@ -16,7 +16,7 @@ from jinja2.environment import get_spontaneous_environment
 from jinja2.runtime import Undefined, concat
 from jinja2.parser import statement_end_tokens
 from jinja2.exceptions import TemplateAssertionError
-from jinja2.utils import import_string
+from jinja2.utils import import_string, Markup
 
 
 # the only real useful gettext functions for a Jinja template.  Note
@@ -286,7 +286,7 @@ def babel_extract(fileobj, keywords, comment_tags, options):
         tuple(extensions),
         # fill with defaults so that environments are shared
         # with other spontaneus environments.
-        True, Undefined, unicode
+        True, Undefined, None, False
     )
 
     node = environment.parse(fileobj.read().decode(encoding))
index c4c108ee3a6d215661d3a8209b8734c8490681d2..15323adaf66995f21b91a4a5e94b5356769e104f 100644 (file)
@@ -51,7 +51,8 @@ def do_forceescape(value):
     return escape(unicode(value))
 
 
-def do_replace(s, old, new, count=None):
+@environmentfilter
+def do_replace(environment, s, old, new, count=None):
     """Return a copy of the value with all occurrences of a substring
     replaced with a new one. The first argument is the substring
     that should be replaced, the second is the replacement string.
@@ -68,6 +69,8 @@ def do_replace(s, old, new, count=None):
     """
     if count is None:
         count = -1
+    if not environment.autoescape:
+        return unicode(s).replace(unicode(old), unicode(new), count)
     if hasattr(old, '__html__') or hasattr(new, '__html__') and \
        not hasattr(s, '__html__'):
         s = escape(s)
@@ -86,7 +89,8 @@ def do_lower(s):
     return soft_unicode(s).lower()
 
 
-def do_xmlattr(d, autospace=False):
+@environmentfilter
+def do_xmlattr(_environment, *args, **kwargs):
     """Create an SGML/XML attribute string based on the items in a dict.
     All values that are neither `none` nor `undefined` are automatically
     escaped:
@@ -107,23 +111,18 @@ def do_xmlattr(d, autospace=False):
         </ul>
 
     As you can see it automatically prepends a space in front of the item
-    if the filter returned something. You can disable this by passing
-    `false` as only argument to the filter.
+    if the filter returned something.
     """
-    if not hasattr(d, 'iteritems'):
-        raise TypeError('a dict is required')
-    result = []
-    for key, value in d.iteritems():
-        if value is not None and not isinstance(value, Undefined):
-            result.append(u'%s="%s"' % (escape(key), escape(value)))
     rv = u' '.join(
         u'%s="%s"' % (escape(key), escape(value))
-        for key, value in d.iteritems()
+        for key, value in dict(*args, **kwargs).iteritems()
         if value is not None and not isinstance(value, Undefined)
     )
-    if autospace:
-        rv = ' ' + rv
-    return Markup(rv)
+    if rv:
+        rv = u' ' + rv
+    if _environment.autoescape:
+        rv = Markup(rv)
+    return rv
 
 
 def do_capitalize(s):
@@ -197,7 +196,8 @@ def do_default(value, default_value=u'', boolean=False):
     return value
 
 
-def do_join(value, d=u''):
+@environmentfilter
+def do_join(environment, value, d=u''):
     """Return a string which is the concatenation of the strings in the
     sequence. The separator between elements is an empty string per
     default, you can define ith with the optional parameter:
@@ -210,6 +210,10 @@ def do_join(value, d=u''):
         {{ [1, 2, 3]|join }}
             -> 123
     """
+    # no automatic escaping?  joining is a lot eaiser then
+    if not environment.autoescape:
+        return unicode(d).join(imap(unicode, value))
+
     # if the delimiter doesn't have an html representation we check
     # if any of the items has.  If yes we do a coercion to Markup
     if not hasattr(d, '__html__'):
index 2bcb30d0872797e3f1d6d1123a6a471ee3a3112f..9744ef4d146bfce966575f0da2ef3df6d3fcdd1e 100644 (file)
@@ -31,10 +31,30 @@ def split_template_path(template):
 
 class BaseLoader(object):
     """Baseclass for all loaders.  Subclass this and override `get_source` to
-    implement a custom loading mechanism.
-
-    The environment provides a `get_template` method that will automatically
-    call the loader bound to an environment.
+    implement a custom loading mechanism.  The environment provides a
+    `get_template` method that calls the loader's `load` method to get the
+    :class:`Template` object.
+
+    A very basic example for a loader that looks up templates on the file
+    system could look like this::
+
+        from jinja2 import BaseLoader, TemplateNotFound
+        from os.path import join, exists, getmtime
+
+        class MyLoader(BaseLoader):
+
+            def __init__(self, path, cache_size=50, auto_reload=True):
+                BaseLoader.__init__(self, cache_size, auto_reload)
+                self.path = path
+
+            def get_source(self, environment, template):
+                path = join(self.path, template)
+                if not exists(path):
+                    raise TemplateNotFound(template)
+                mtime = getmtime(path)
+                with file(path) as f:
+                    source = f.read().decode('utf-8')
+                return source, path, lambda: mtime != getmtime(path)
     """
 
     def __init__(self, cache_size=50, auto_reload=True):
@@ -67,8 +87,11 @@ class BaseLoader(object):
         raise TemplateNotFound(template)
 
     def load(self, environment, name, globals=None):
-        """Loads a template.  This method should not be overriden by
-        subclasses unless `get_source` doesn't provide enough flexibility.
+        """Loads a template.  This method looks up the template in the cache
+        or loads one by calling :meth:`get_source`.  Subclasses should not
+        override this method as loaders working on collections of other
+        loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
+        will not call this method but `get_source` directly.
         """
         if globals is None:
             globals = {}
@@ -88,14 +111,26 @@ class BaseLoader(object):
 
 
 class FileSystemLoader(BaseLoader):
-    """Loads templates from the file system."""
+    """Loads templates from the file system.  This loader can find templates
+    in folders on the file system and is the preferred way to load them.
+
+    The loader takes the path to the templates as string, or if multiple
+    locations are wanted a list of them which is then looked up in the
+    given order:
+
+    >>> loader = FileSystemLoader('/path/to/templates')
+    >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
+
+    Per default the template encoding is ``'utf-8'`` which can be changed
+    by setting the `encoding` parameter to something else.
+    """
 
     def __init__(self, searchpath, encoding='utf-8', cache_size=50,
                  auto_reload=True):
         BaseLoader.__init__(self, cache_size, auto_reload)
         if isinstance(searchpath, basestring):
             searchpath = [searchpath]
-        self.searchpath = searchpath
+        self.searchpath = list(searchpath)
         self.encoding = encoding
 
     def get_source(self, environment, template):
@@ -115,34 +150,65 @@ class FileSystemLoader(BaseLoader):
 
 
 class PackageLoader(BaseLoader):
-    """Load templates from python eggs."""
+    """Load templates from python eggs or packages.  It is constructed with
+    the name of the python package and the path to the templates in that
+    package:
+
+    >>> loader = PackageLoader('mypackage', 'views')
 
-    def __init__(self, package_name, package_path, charset='utf-8',
-                 cache_size=50, auto_reload=True):
+    If the package path is not given, ``'templates'`` is assumed.
+
+    Per default the template encoding is ``'utf-8'`` which can be changed
+    by setting the `encoding` parameter to something else.  Due to the nature
+    of eggs it's only possible to reload templates if the package was loaded
+    from the file system and not a zip file.
+    """
+
+    def __init__(self, package_name, package_path='templates',
+                 encoding='utf-8', cache_size=50, auto_reload=True):
         BaseLoader.__init__(self, cache_size, auto_reload)
-        import pkg_resources
-        self._pkg = pkg_resources
-        self.package_name = package_name
+        from pkg_resources import DefaultProvider, ResourceManager, get_provider
+        provider = get_provider(package_name)
+        self.encoding = encoding
+        self.manager = ResourceManager()
+        self.filesystem_bound = isinstance(provider, DefaultProvider)
+        self.provider = provider
         self.package_path = package_path
 
     def get_source(self, environment, template):
         pieces = split_template_path(template)
-        path = '/'.join((self.package_path,) + tuple(pieces))
-        if not self._pkg.resource_exists(self.package_name, path):
+        p = '/'.join((self.package_path,) + tuple(pieces))
+        if not self.provider.has_resource(p):
             raise TemplateNotFound(template)
-        return self._pkg.resource_string(self.package_name, path), None, None
+
+        filename = uptodate = None
+        if self.filesystem_bound:
+            filename = self.provider.get_resource_filename(self.manager, p)
+            mtime = path.getmtime(filename)
+            def uptodate():
+                return path.getmtime(filename) != mtime
+
+        source = self.provider.get_resource_string(self.manager, p)
+        return source.decode(self.encoding), filename, uptodate
 
 
 class DictLoader(BaseLoader):
-    """Loads a template from a python dict.  Used for unittests mostly."""
+    """Loads a template from a python dict.  It's passed a dict of unicode
+    strings bound to template names.  This loader is useful for unittesting:
+
+    >>> loader = DictLoader({'index.html': 'source here'})
+
+    Because auto reloading is rarely useful this is disabled per default.
+    """
 
-    def __init__(self, mapping, cache_size=50):
-        BaseLoader.__init__(self, cache_size, False)
+    def __init__(self, mapping, cache_size=50, auto_reload=False):
+        BaseLoader.__init__(self, cache_size, auto_reload)
         self.mapping = mapping
 
     def get_source(self, environment, template):
         if template in self.mapping:
-            return self.mapping[template], None, None
+            source = self.mapping[template]
+            return source, None, lambda: source != self.mapping[template]
         raise TemplateNotFound(template)
 
 
@@ -151,6 +217,17 @@ class FunctionLoader(BaseLoader):
     function becomes the name of the template passed and has to return either
     an unicode string with the template source, a tuple in the form ``(source,
     filename, uptodatefunc)`` or `None` if the template does not exist.
+
+    >>> def load_template(name):
+    ...     if name == 'index.html'
+    ...         return '...'
+    ...
+    >>> loader = FunctionLoader(load_template)
+
+    The `uptodatefunc` is a function that is called if autoreload is enabled
+    and has to return `True` if the template is still up to date.  For more
+    details have a look at :meth:`BaseLoader.get_source` which has the same
+    return value.
     """
 
     def __init__(self, load_func, cache_size=50, auto_reload=True):
@@ -170,7 +247,15 @@ class PrefixLoader(BaseLoader):
     """A loader that is passed a dict of loaders where each loader is bound
     to a prefix.  The caching is independent of the actual loaders so the
     per loader cache settings are ignored.  The prefix is delimited from the
-    template by a slash.
+    template by a slash:
+
+    >>> loader = PrefixLoader({
+    ...     'app1':     PackageLoader('mypackage.app1'),
+    ...     'app2':     PackageLoader('mypackage.app2')
+    ... })
+
+    By loading ``'app1/index.html'`` the file from the app1 package is loaded,
+    by loading ``'app2/index.html'`` the file from the second.
     """
 
     def __init__(self, mapping, delimiter='/', cache_size=50,
@@ -193,6 +278,14 @@ class ChoiceLoader(BaseLoader):
     specified.  If a template could not be found by one loader the next one
     is tried.  Like for the `PrefixLoader` the cache settings of the actual
     loaders don't matter as the choice loader does the caching.
+
+    >>> loader = ChoiceLoader([
+    ...     FileSystemLoader('/path/to/user/templates'),
+    ...     PackageLoader('myapplication')
+    ])
+
+    This is useful if you want to allow users to override builtin templates
+    from a different location.
     """
 
     def __init__(self, loaders, cache_size=50, auto_reload=True):
index 5f8de1f1226acf5153583789693ab698cdab7a8b..b28950050e5421ed646d6fada4ae0632bb3922ac 100644 (file)
@@ -9,13 +9,15 @@
     :license: GNU GPL.
 """
 from types import FunctionType
-from jinja2.utils import Markup, partial
-from jinja2.exceptions import UndefinedError
+from itertools import chain, imap
+from jinja2.utils import Markup, partial, soft_unicode, escape
+from jinja2.exceptions import UndefinedError, TemplateRuntimeError
 
 
 # these variables are exported to the template runtime
 __all__ = ['LoopContext', 'TemplateContext', 'TemplateReference', 'Macro',
-           'Markup', 'missing', 'concat']
+           'TemplateRuntimeError', 'Markup', 'missing', 'concat', 'escape',
+           'markup_join', 'unicode_join']
 
 
 # special singleton representing missing values for the runtime
@@ -26,6 +28,22 @@ missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
 concat = u''.join
 
 
+def markup_join(*args):
+    """Concatenation that escapes if necessary and converts to unicode."""
+    buf = []
+    iterator = imap(soft_unicode, args)
+    for arg in iterator:
+        buf.append(arg)
+        if hasattr(arg, '__html__'):
+            return Markup(u'').join(chain(buf, iterator))
+    return concat(buf)
+
+
+def unicode_join(*args):
+    """Simple args to unicode conversion and concatenation."""
+    return concat(imap(unicode, args))
+
+
 class TemplateContext(object):
     """Holds the variables of the local template or of the global one.  It's
     not save to use this class outside of the compiled code.  For example
@@ -68,7 +86,8 @@ class TemplateContext(object):
         except LookupError:
             return self.environment.undefined('there is no parent block '
                                               'called %r.' % name)
-        render = lambda: Markup(concat(blocks[pos](self)))
+        wrap = self.environment.autoescape and Markup or (lambda x: x)
+        render = lambda: wrap(concat(blocks[pos](self)))
         render.__name__ = render.name = name
         return render
 
@@ -123,7 +142,9 @@ class TemplateReference(object):
 
     def __getitem__(self, name):
         func = self.__context.blocks[name][-1]
-        render = lambda: Markup(concat(func(self.__context)))
+        wrap = self.__context.environment.autoescape and \
+               Markup or (lambda x: x)
+        render = lambda: wrap(concat(func(self.__context)))
         render.__name__ = render.name = name
         return render
 
@@ -269,9 +290,18 @@ def fail_with_undefined_error(self, *args, **kwargs):
 
 
 class Undefined(object):
-    """The default undefined implementation.  This undefined implementation
-    can be printed and iterated over, but every other access will raise a
-    `NameError`.  Custom undefined classes must subclass this.
+    """The default undefined type.  This undefined type can be printed and
+    iterated over, but every other access will raise an :exc:`UndefinedError`:
+
+    >>> foo = Undefined(name='foo')
+    >>> str(foo)
+    ''
+    >>> not foo
+    True
+    >>> foo + 42
+    Traceback (most recent call last):
+      ...
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
     """
     __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name')
 
@@ -307,7 +337,18 @@ class Undefined(object):
 
 
 class DebugUndefined(Undefined):
-    """An undefined that returns the debug info when printed."""
+    """An undefined that returns the debug info when printed.
+
+    >>> foo = DebugUndefined(name='foo')
+    >>> str(foo)
+    '{{ foo }}'
+    >>> not foo
+    True
+    >>> foo + 42
+    Traceback (most recent call last):
+      ...
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
+    """
     __slots__ = ()
 
     def __unicode__(self):
@@ -325,6 +366,20 @@ class StrictUndefined(Undefined):
     """An undefined that barks on print and iteration as well as boolean
     tests and all kinds of comparisons.  In other words: you can do nothing
     with it except checking if it's defined using the `defined` test.
+
+    >>> foo = StrictUndefined(name='foo')
+    >>> str(foo)
+    Traceback (most recent call last):
+      ...
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
+    >>> not foo
+    Traceback (most recent call last):
+      ...
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
+    >>> foo + 42
+    Traceback (most recent call last):
+      ...
+    jinja2.exceptions.UndefinedError: 'foo' is undefined
     """
     __slots__ = ()
     __iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \