again documentation updates fixed another python 2.4 bug. Imports are not evaluated...
authorArmin Ronacher <armin.ronacher@active-4.com>
Tue, 29 Apr 2008 11:43:16 +0000 (13:43 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Tue, 29 Apr 2008 11:43:16 +0000 (13:43 +0200)
--HG--
branch : trunk

docs/api.rst
docs/templates.rst
jinja2/compiler.py
jinja2/environment.py
jinja2/ext.py
jinja2/nodes.py
jinja2/runtime.py
jinja2/utils.py

index 7dacf368174bbfa4cda537640524e82be04f4102..18ef9a21b2bb13e58dc3d066dfe6f6d71471cd8b 100644 (file)
@@ -83,7 +83,7 @@ High Level API
 
 
 .. autoclass:: jinja2.Template
-    :members: render, stream, generate, include
+    :members: render, stream, generate, module
 
 
 .. autoclass:: jinja2.environment.TemplateStream
index 9700cdaec1fb3065747de50460bcb39fe9097882..384d27ec0a05906abc8924dedf7e643714cf66dd 100644 (file)
@@ -1,6 +1,8 @@
 Template Designer Documentation
 ===============================
 
+.. highlight:: html+jinja
+
 This document describes the syntax and semantics of the template engine and
 will be most useful as reference to those creating Jinja templates.  As the
 template engine is very flexible the configuration from the application might
@@ -20,9 +22,7 @@ values when the template is evaluated, and tags, which control the logic of
 the template.  The template syntax is heavily inspired by Django and Python.
 
 Below is a minimal template that illustrates a few basics.  We will cover
-the details later in that document:
-
-.. sourcecode:: html+jinja
+the details later in that document::
 
     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
     <html lang="en">
@@ -60,9 +60,7 @@ those.
 
 You can use a dot (``.``) to access attributes of a variable, alternative the
 so-called "subscribe" syntax (``[]``) can be used.  The following lines do
-the same:
-
-.. sourcecode:: jinja
+the same::
 
     {{ foo.bar }}
     {{ foo['bar'] }}
@@ -106,9 +104,7 @@ which will then return true or false depening on if `name` is defined.
 
 Tests can accept arguments too.  If the test only takes one argument you can
 leave out the parentheses to group them.  For example the following two
-expressions do the same:
-
-.. sourcecode:: jinja
+expressions do the same::
 
     {% if loop.index is divisibleby 3 %}
     {% if loop.index is divisibleby(3) %}
@@ -122,9 +118,7 @@ Comments
 To comment-out part of a line in a template, use the comment syntax which is
 by default set to ``{# ... #}``.  This is useful to comment out parts of the
 template for debugging or to add information for other template designers or
-yourself:
-
-.. sourcecode:: jinja
+yourself::
 
     {# note: disabled template because we no longer user this
         {% for user in users %}
@@ -132,6 +126,7 @@ yourself:
         {% endfor %}
     #}
 
+.. _template-inheritance:
 
 Template Inheritance
 --------------------
@@ -149,9 +144,7 @@ Base Template
 
 This template, which we'll call ``base.html``, defines a simple HTML skeleton
 document that you might use for a simple two-column page. It's the job of
-"child" templates to fill the empty blocks with content:
-
-.. sourcecode:: html+jinja
+"child" templates to fill the empty blocks with content::
 
     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
     <html lang="en">
@@ -178,9 +171,7 @@ child template may override those portions of the template.
 Child Template
 ~~~~~~~~~~~~~~
 
-A child template might look like this:
-
-.. sourcecode:: html+jinja
+A child template might look like this::
 
     {% extends "base.html" %}
     {% block title %}Index{% endblock %}
@@ -205,9 +196,7 @@ may cause confusion.
 
 The filename of the template depends on the template loader.  For example the
 :class:`FileSystemLoader` allows you to access other templates by giving the
-filename.  You can access templates in subdirectories with an slash:
-
-.. sourcecode:: jinja
+filename.  You can access templates in subdirectories with an slash::
 
     {% extends "layout/default.html" %}
 
@@ -223,9 +212,7 @@ were two similarly-named ``{% block %}`` tags in a template, that template's
 parent wouldn't know which one of the blocks' content to use.
 
 If you want to print a block multiple times you can however use the special
-`self` variable and call the block with that name:
-
-.. sourcecode:: jinja
+`self` variable and call the block with that name::
 
     <title>{% block title %}{% endblock %}</title>
     <h1>{{ self.title() }}</h1>
@@ -240,9 +227,7 @@ Super Blocks
 ~~~~~~~~~~~~
 
 It's possible to render the contents of the parent block by calling `super`.
-This gives back the results of the parent block:
-
-.. sourcecode:: jinja
+This gives back the results of the parent block::
 
     {% block sidebar %}
         <h3>Table Of Contents</h3>
@@ -310,13 +295,11 @@ program - conditionals (i.e. if/elif/else), for-loops, as well as things like
 macros and blocks.  Control structures appear inside ``{% ... %}`` blocks
 in the default syntax.
 
-For Loops
-~~~~~~~~~
+For
+~~~
 
 Loop over each item in a sequece.  For example, to display a list of users
-provided in a variable called `users`:
-
-.. sourcecode:: html+jinja
+provided in a variable called `users`::
 
     <h1>Members</h1>
     <ul>
@@ -351,9 +334,7 @@ Inside of a for loop block you can access some special variables:
 +-----------------------+---------------------------------------------------+
 
 Within a for-loop, it's psosible to cycle among a list of strings/variables
-each time through the loop by using the special `loop.cycle` helper:
-
-.. sourcecode:: html+jinja
+each time through the loop by using the special `loop.cycle` helper::
 
     {% for row in rows %}
         <li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
@@ -363,9 +344,7 @@ each time through the loop by using the special `loop.cycle` helper:
 
 Unlike in Python it's not possible to `break` or `continue` in a loop.  You
 can however filter the sequence during iteration which allows you to skip
-items.  The following example skips all the users which are hidden:
-
-.. sourcecode:: html+jinja
+items.  The following example skips all the users which are hidden::
 
     {% for user in users if not user.hidden %}
         <li>{{ user.username|e }}</li>
@@ -376,9 +355,7 @@ not counting the users not iterated over.
 
 If no iteration took place because the sequence was empty or the filtering
 removed all the items from the sequence you can render a replacement block
-by using `else`:
-
-.. sourcecode:: html+jinja
+by using `else`::
 
     <ul>
     {% for user in users %}
@@ -394,9 +371,7 @@ If
 
 The `if` statement in Jinja is comparable with the if statements of Python.
 In the simplest form you can use it to test if a variable is defined, not
-empty or not false:
-
-.. sourcecode:: html+jinja
+empty or not false::
 
     {% if users %}
     <ul>
@@ -407,9 +382,7 @@ empty or not false:
     {% endif %}
 
 For multiple branches `elif` and `else` can be used like in Python.  You can
-use more complex :ref:`expressions` there too.
-
-.. sourcecode:: html+jinja
+use more complex :ref:`expressions` there too::
 
     {% if kenny.sick %}
         Kenny is sick.
@@ -423,6 +396,215 @@ If can also be used as :ref:`inline expression <if-expression>` and for
 :ref:`loop filtering <loop-filtering>`.
 
 
+Macros
+~~~~~~
+
+Macros comparable with functions in regular programming languages.  They are
+useful to put often used idoms into reusable functions to not repeat yourself.
+
+Macros can be defined in helper templates with then are :ref:`imported <import>`
+or directly in the template where they are used.  There is one big difference
+between those two possibilities.  A macro that is defined in the template where
+it's also used has access to the context passed to the template.  A macro
+defined in another template and then imported can only access variables defined
+there or in the global context.
+
+Here a small example of a macro that renders a form element::
+
+    {% macro input(name, value='', type='text', size=20) -%}
+        <input type="{{ type }}" name="{{ name }}" value="{{
+            value|e }}" size="{{ size }}">
+    {%- endmacro %}
+
+The macro can then be called like a function in the namespace::
+
+    <p>{{ input('username') }}</p>
+    <p>{{ input('password', type='password') }}</p>
+
+If the macro was defiend in a different template you have to
+:ref:`import <import>` it first.
+
+Inside macros you have access to three special variables:
+
+`varargs`
+    If more positional arguments are passed to the macro then accepted by the
+    macro they end up in the special `varargs` variable as list of values.
+
+`kwargs`
+    Like `varargs` but for keyword arguments.  All unconsumed keyword
+    arguments are stored in this special variable.
+
+`caller`
+    If the macro was called from a :ref:`call` tag the caller is stored in
+    this variable as macro which can be called.
+
+Macros also expose some of their internal details.  The following attributes
+are available on a macro object:
+
+`name`
+    The name of the macro.  ``{{ input.name }}`` will print ``input``.
+
+`arguments`
+    A tuple of the names of arguments the macro accepts.
+
+`defaults`
+    A tuple of default values.
+
+`catch_kwargs`
+    This is `true` if the macro accepts extra keyword arguments (ie: accesses
+    the special `kwargs` variable).
+
+`catch_varargs`
+    This is `true` if the macro accepts extra positional arguments (ie:
+    accesses the special `varargs` variable).
+
+`caller`
+    This is `true` if the macro accesses the special `caller` variable and may
+    be called from a :ref:`call` tag.
+
+
+.. _call:
+
+Call
+~~~~
+
+In some cases it can be useful to pass a macro to another macro.  For this
+purpose you can use the special `call` block.  The following example shows
+a macro that takes advantage of the call functionality and how it can be
+used::
+
+    {% macro render_dialog(title, class='dialog') -%}
+        <div class="{{ class }}">
+            <h2>{{ title }}</h2>
+            <div class="contents">
+                {{ caller() }}
+            </div>
+        </div>
+    {%- endmacro %}
+
+    {% call render_dialog('Hello World') %}
+        This is a simple dialog rendered by using a macro and
+        a call block.
+    {% endcall %}
+
+It's also possible to pass arguments back to the call block.  This makes it
+useful as replacement for loops.  It is however not possible to call a
+call block with another call block.
+
+Here an example of how a call block can be used with arguments::
+
+    {% macro dump_users(users) -%}
+        <ul>
+        {%- for user in users %}
+            <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
+        {%- endfor %}
+        </ul>
+    {%- endmacro %}
+
+    {% call(user) dump_users(list_of_user) %}
+        <dl>
+            <dl>Realname</dl>
+            <dd>{{ user.realname|e }}</dd>
+            <dl>Description</dl>
+            <dd>{{ user.description }}</dd>
+        </dl>
+    {% endcall %}
+
+
+Assignments
+~~~~~~~~~~~
+
+Inside code blocks you can also assign values to variables.  Assignments at
+top level (outside of blocks, macros or loops) are exported from the template
+like top level macros and can be imported by other templates.
+
+Assignments are just written in code blocks like any other statement just
+without explicit keyword::
+
+    {% navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
+
+
+Extends
+~~~~~~~
+
+The `extends` tag can be used to extend a template from another one.  You
+can have multiple of them in a file but only one of them may be executed
+at the time.  There is no support for multiple inheritance.  See the section
+about :ref:`template-inheritance` above.
+
+
+Block
+~~~~~
+
+Blocks are used for inheritance and act as placeholders and replacements
+at the same time.  They are documented in detail as part of the section
+about :ref:`template-inheritance`.
+
+
+Include
+~~~~~~~
+
+The `include` statement is useful to include a template and return the
+rendered contents of that file into the current namespace::
+
+    {% include 'header.html' %}
+        Body
+    {% include 'footer.html' %}
+
+Included templates have access to the current template variables minus local
+modifications.
+
+.. _import:
+
+Import
+~~~~~~
+
+Jinja2 supports putting often used code into macros.  These macros can go into
+different templates and get imported from there.  This works similar to the
+import statements in Python.  It's important to know that imports are cached
+and imported templates don't have access to the current template variables,
+just the globals.
+
+There are two ways to import templates.  You can import the complete template
+into a variable or request specific macros / exported variables from it.
+
+Imagine we have a helper module that renders forms (called `forms.html`)::
+
+    {% macro input(name, value='', type='text') -%}
+        <input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
+    {%- endmacro %}
+
+    {%- macro textarea(name, value='', rows=10, cols=40) -%}
+        <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
+            }}">{{ value|e }}</textarea>
+    {%- endmacro %}
+
+The easiest and most flexible is importing the whole module into a variable.
+That way you can access the attributes::
+
+    {% import 'forms.html' as forms %}
+    <dl>
+        <dt>Username</dt>
+        <dd>{{ forms.input('username') }}</dd>
+        <dt>Password</dt>
+        <dd>{{ forms.input('password', type='password') }}</dd>
+    </dl>
+    <p>{{ forms.textarea('comment') }}</p>
+
+
+Alternatively you can import names from the template into the current
+namespace::
+
+    {% from 'forms.html' import input as input_field, textarea %}
+    <dl>
+        <dt>Username</dt>
+        <dd>{{ input_field('username') }}</dd>
+        <dt>Password</dt>
+        <dd>{{ input_field('password', type='password') }}</dd>
+    </dl>
+    <p>{{ textarea('comment') }}</p>
+
+
 .. _expressions:
 
 Expressions
@@ -453,9 +635,7 @@ for Python objects such as strings and numbers.  The following literals exist:
 ['list', 'of', 'objects']:
     Everything between two brackets is a list.  Lists are useful to store
     sequential data in or to iterate over them.  For example you can easily
-    create a list of links using lists and tuples with a for loop.
-
-    .. sourcecode:: html+jinja
+    create a list of links using lists and tuples with a for loop::
 
         <ul>
         {% for href, caption in [('index.html', 'Index'), ('about.html', 'About'),
@@ -580,12 +760,13 @@ If Expression
 
 It is also possible to use inline `if` expressions.  These are useful in some
 situations.  For example you can use this to extend from one template if a
-variable is defined, otherwise from the default layout template:
-
-.. sourcecode:: jinja
+variable is defined, otherwise from the default layout template::
 
     {% extends layout_template if layout_template is defined else 'master.html' %}
 
+The general syntax is ``<do something> if <something is true> else <do
+something else>``.
+
 
 .. _builtin-filters:
 
index f9f11b3fdff73cd2ef12b1371989978dd5c4d6e7..b40c1d16489448f774ffd238728645b3c450224b 100644 (file)
@@ -720,7 +720,7 @@ class CodeGenerator(NodeVisitor):
             self.write('context.vars[%r] = ' % node.target)
         self.write('environment.get_template(')
         self.visit(node.template, frame)
-        self.write(', %r).include(context)' % self.name)
+        self.write(', %r).module' % self.name)
         if frame.toplevel and not node.target.startswith('__'):
             self.writeline('context.exported_vars.discard(%r)' % node.target)
 
@@ -729,7 +729,7 @@ class CodeGenerator(NodeVisitor):
         self.newline(node)
         self.write('included_template = environment.get_template(')
         self.visit(node.template, frame)
-        self.write(', %r).include(context)' % self.name)
+        self.write(', %r).module' % self.name)
         for name in node.names:
             if isinstance(name, tuple):
                 name, alias = name
@@ -1051,6 +1051,11 @@ class CodeGenerator(NodeVisitor):
             frame.assigned_names.add(node.name)
         self.write('l_' + node.name)
 
+    def visit_MarkSafe(self, node, frame):
+        self.write('Markup(')
+        self.visit(node.expr, frame)
+        self.write(')')
+
     def visit_Const(self, node, frame):
         val = node.value
         if isinstance(val, float):
index 7f4144be014ef403fc5d37c782b0daac2dcb2af6..919ee9aaa9fed841411de8d5ddd06d0df1a7f31f 100644 (file)
@@ -425,32 +425,22 @@ class Template(object):
         return TemplateContext(self.environment, parent, self.name,
                                self.blocks)
 
-    def include(self, vars=None):
-        """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)
+    @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'
-        >>> i.say_hello('John')
-        u'Hello John!'
         """
-        if isinstance(vars, TemplateContext):
-            context = TemplateContext(self.environment, vars.parent,
-                                      self.name, self.blocks)
-        else:
-            context = self.new_context(vars)
-        return IncludedTemplate(self, context)
+        if hasattr(self, '_module'):
+            return self._module
+        self._module = rv = TemplateModule(self, self.new_context())
+        return rv
 
     def get_corresponding_lineno(self, lineno):
         """Return the source line number of a line number in the
@@ -482,8 +472,8 @@ class Template(object):
         return '<%s %s>' % (self.__class__.__name__, name)
 
 
-class IncludedTemplate(object):
-    """Represents an included template.  All the exported names of the
+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.
     """
@@ -546,7 +536,7 @@ class TemplateStream(object):
                         c_size += 1
                 except StopIteration:
                     if not c_size:
-                        raise
+                        return
                 yield concat(buf)
                 del buf[:]
                 c_size = 0
index 1158505dc8eb048ffcf6b3154663fd766722d5fe..bc04a6972baab9e5d072a142172b8232a7d7e55b 100644 (file)
@@ -199,8 +199,6 @@ class TransExtension(Extension):
             gettext = nodes.Name('gettext', 'load')
             node = nodes.Call(gettext, [nodes.Const(singular)],
                               [], None, None)
-            if variables:
-                node = nodes.Mod(node, variables)
 
         # singular and plural
         else:
@@ -210,8 +208,14 @@ class TransExtension(Extension):
                 nodes.Const(plural),
                 plural_expr
             ], [], None, None)
-            if variables:
-                node = nodes.Mod(node, variables)
+
+        # mark the return value as safe if we are in an
+        # environment with autoescaping turned on
+        if self.environment.autoescape:
+            node = nodes.MarkSafe(node)
+
+        if variables:
+            node = nodes.Mod(node, variables)
         return nodes.Output([node])
 
 
index c1231ea9c561b11a5250217889c8827749b5f402..b3255d54f11f9b35445b00f3727d7d8d47535809 100644 (file)
@@ -17,6 +17,7 @@ from types import FunctionType
 from itertools import chain, izip
 from collections import deque
 from copy import copy
+from jinja2.utils import Markup
 
 
 _binop_to_func = {
@@ -349,6 +350,14 @@ class Name(Expr):
         return self.name not in ('true', 'false', 'none')
 
 
+class MarkSafe(Expr):
+    """Mark the wrapped expression as safe (Markup)"""
+    fields = ('expr',)
+
+    def as_const(self):
+        return Markup(self.expr.as_const())
+
+
 class Literal(Expr):
     """Baseclass for literals."""
 
index 0aad4bdb3e5e9b6d299ca2b7acb47aca4e173796..f4a2f1aceb19aa34f637fab3052c7cf254fc6045 100644 (file)
@@ -39,7 +39,7 @@ except TypeError, e:
     else:
         def concat(gen):
             try:
-                return u''.join(list(gen()))
+                return u''.join(list(gen))
             except:
                 exc_type, exc_value, tb = sys.exc_info()
                 raise exc_type, exc_value, tb.tb_next
@@ -233,17 +233,16 @@ class Macro(object):
                  catch_kwargs, catch_varargs, caller):
         self._environment = environment
         self._func = func
+        self._argument_count = len(arguments)
         self.name = name
         self.arguments = arguments
-        self.argument_count = len(arguments)
         self.defaults = defaults
         self.catch_kwargs = catch_kwargs
         self.catch_varargs = catch_varargs
         self.caller = caller
 
     def __call__(self, *args, **kwargs):
-        self.argument_count = len(self.arguments)
-        if not self.catch_varargs and len(args) > self.argument_count:
+        if not self.catch_varargs and len(args) > self._argument_count:
             raise TypeError('macro %r takes not more than %d argument(s)' %
                             (self.name, len(self.arguments)))
         arguments = []
@@ -255,7 +254,7 @@ class Macro(object):
                     value = kwargs.pop(name)
                 except KeyError:
                     try:
-                        value = self.defaults[idx - self.argument_count]
+                        value = self.defaults[idx - self._argument_count]
                     except IndexError:
                         value = self._environment.undefined(
                             'parameter %r was not provided' % name)
@@ -275,7 +274,7 @@ class Macro(object):
             raise TypeError('macro %r takes no keyword argument %r' %
                             (self.name, iter(kwargs).next()))
         if self.catch_varargs:
-            arguments.append(args[self.argument_count:])
+            arguments.append(args[self._argument_count:])
         return self._func(*arguments)
 
     def __repr__(self):
index f37552945435ab394f9aecc1738fbd4b0c9bd6e2..5d8ca483d3fbb12e102cc575d30d6276df4ab5b8 100644 (file)
@@ -26,16 +26,18 @@ _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
 
 
 def contextfunction(f):
-    """Mark a callable as context callable.  A context callable is passed
-    the active context as first argument.
+    """This decorator can be used to mark a callable as context callable.  A
+    context callable is passed the active context as first argument if it
+    was directly stored in the context.
     """
     f.contextfunction = True
     return f
 
 
 def environmentfunction(f):
-    """Mark a callable as environment callable.  An environment callable is
-    passed the current environment as first argument.
+    """This decorator can be used to mark a callable as environment callable.
+    A environment callable is passed the current environment as first argument
+    if it was directly stored in the context.
     """
     f.environmentfunction = True
     return f