some more documentation updates and minor code cleanups. Additionally True and true...
authorArmin Ronacher <armin.ronacher@active-4.com>
Wed, 28 May 2008 09:26:59 +0000 (11:26 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Wed, 28 May 2008 09:26:59 +0000 (11:26 +0200)
--HG--
branch : trunk

16 files changed:
docs/_static/implementation.png [new file with mode: 0644]
docs/_static/note.png [new file with mode: 0644]
docs/_static/style.css
docs/api.rst
docs/sandbox.rst
docs/switching.rst
docs/templates.rst
ext/jinja.el
jinja2/__init__.py
jinja2/_speedups.c
jinja2/filters.py
jinja2/nodes.py
jinja2/parser.py
jinja2/runtime.py
jinja2/utils.py
tests/test_syntax.py

diff --git a/docs/_static/implementation.png b/docs/_static/implementation.png
new file mode 100644 (file)
index 0000000..21deae6
Binary files /dev/null and b/docs/_static/implementation.png differ
diff --git a/docs/_static/note.png b/docs/_static/note.png
new file mode 100644 (file)
index 0000000..c484da6
Binary files /dev/null and b/docs/_static/note.png differ
index 8bfa23e240f0764a6f191eb2efbc82167287ac35..c5894f6b63014202851dbc62c553f9a1d4ad2901 100644 (file)
@@ -209,20 +209,25 @@ cite {
 
 div.admonition {
     margin: 10px 0 10px 0;
-    padding: 10px;
+    padding: 10px 10px 10px 60px;
     border: 1px solid #ccc;
-    background-color: #f8f8f8;
 }
 
 div.admonition p.admonition-title {
-    margin: -3px 0 5px 0;
+    background-color: #b41717;
+    color: white;
+    margin: -10px -10px 10px -60px;
+    padding: 4px 10px 4px 10px;
     font-weight: bold;
-    color: #b41717;
-    font-size: 16px;
+    font-size: 15px;
+}
+
+div.admonition-note {
+    background: url(note.png) no-repeat 10px 40px;
 }
 
-div.admonition p {
-    margin: 0 0 0 40px;
+div.admonition-implementation {
+    background: url(implementation.png) no-repeat 10px 40px;
 }
 
 #toc {
@@ -296,7 +301,8 @@ table.indextable dl dd a {
 dl.function dt,
 dl.class dt,
 dl.exception dt,
-dl.method dt {
+dl.method dt,
+dl.attribute dt {
     font-weight: normal;
 }
 
index cc9e9d82a4da919775946b2fe5b88c9da6a0810b..0dce61876d7b4706cd6e163f4afa89fbd473b205 100644 (file)
@@ -95,6 +95,11 @@ For more details about unicode in Python have a look at the excellent
 High Level API
 --------------
 
+The high-level API is the API you will use in the application to load and
+render Jinja2 templates.  The :ref:`low-level-api` on the other side is only
+useful if you want to dig deeper into Jinja2 or :ref:`develop extensions
+<jinja-extensions>`.
+
 .. autoclass:: Environment([options])
     :members: from_string, get_template, join_path, extend
 
@@ -171,7 +176,7 @@ High Level API
         possibility to enhance the error message.
 
 .. autoclass:: Template
-    :members: make_module, module, new_context
+    :members: module, make_module
 
     .. attribute:: globals
 
@@ -233,12 +238,62 @@ disallows all operations beside testing if it's an undefined object.
 
 .. autoclass:: jinja2.runtime.Undefined()
 
+    .. attribute:: _undefined_hint
+
+        Either `None` or an unicode string with the error message for
+        the undefined object.
+
+    .. attribute:: _undefined_obj
+
+        Either `None` or the owner object that caused the undefined object
+        to be created (for example because an attribute does not exist).
+
+    .. attribute:: _undefined_name
+
+        The name for the undefined variable / attribute or just `None`
+        if no such information exists.
+
+    .. attribute:: _undefined_exception
+
+        The exception that the undefined object wants to raise.  This
+        is usually one of :exc:`UndefinedError` or :exc:`SecurityError`.
+
+    .. method:: _fail_with_undefined_error(\*args, \**kwargs)
+
+        When called with any arguments this method raises
+        :attr:`_undefined_exception` with an error message generated
+        from the undefined hints stored on the undefined object.
+
 .. autoclass:: jinja2.runtime.DebugUndefined()
 
 .. autoclass:: jinja2.runtime.StrictUndefined()
 
 Undefined objects are created by calling :attr:`undefined`.
 
+.. admonition:: Implementation
+
+    :class:`Undefined` objects are implemented by overriding the special
+    `__underscore__` methods.  For example the default :class:`Undefined`
+    class implements `__unicode__` in a way that it returns an empty
+    string, however `__int__` and others still fail with an exception.  To
+    allow conversion to int by returning ``0`` you can implement your own::
+
+        class NullUndefined(Undefined):
+            def __int__(self):
+                return 0
+            def __float__(self):
+                return 0.0
+
+    To disallow a method, just override it and raise
+    :attr:`_undefined_exception`.  Because this is a very common idom in
+    undefined objects there is the helper method
+    :meth:`_fail_with_undefined_error`.  That does that automatically.  Here
+    a class that works like the regular :class:`UndefinedError` but chokes
+    on iteration::
+
+        class NonIterableUndefined(Undefined):
+            __iter__ = Undefined._fail_with_undefined_error
+
 
 The Context
 -----------
@@ -283,6 +338,20 @@ The Context
         blocks registered.  The last item in each list is the current active
         block (latest in the inheritance chain).
 
+    .. automethod:: jinja2.runtime.Context.call(callable, \*args, \**kwargs)
+
+
+.. admonition:: Implementation
+
+    Context is immutable for the same reason Python's frame locals are
+    immutable inside functions.  Both Jinja2 and Python are not using the
+    context / frame locals as data storage for variables but only as primary
+    data source.
+
+    When a template accesses a variable the template does not define, Jinja2
+    looks up the variable in the context, after that the variable is treated
+    as if it was defined in the template.
+
 
 .. _loaders:
 
@@ -330,13 +399,17 @@ functions to a Jinja2 environment.
 
 .. function:: escape(s)
 
-    Convert the characters &, <, >, and " in string s to HTML-safe sequences.
-    Use this if you need to display text that might contain such characters
-    in HTML.  This function will not escaped objects that do have an HTML
-    representation such as already escaped data.
+    Convert the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in string `s`
+    to HTML-safe sequences.  Use this if you need to display text that might
+    contain such characters in HTML.  This function will not escaped objects
+    that do have an HTML representation such as already escaped data.
+
+    The return value is a :class:`Markup` string.
 
 .. autofunction:: jinja2.utils.clear_caches
 
+.. autofunction:: jinja2.utils.is_undefined
+
 .. autoclass:: jinja2.utils.Markup
 
 
@@ -482,6 +555,8 @@ exist that are variables available to a specific template that are available
 to all :meth:`~Template.render` calls.
 
 
+.. _low-level-api:
+
 Low Level API
 -------------
 
@@ -518,3 +593,10 @@ don't recommend using any of those.
 
     This attribute is `False` if there is a newer version of the template
     available, otherwise `True`.
+
+.. admonition:: Note
+
+    The low-level API is fragile.  Future Jinja2 versions will not change it
+    in a backwards incompatible way but modifications in the Jinja core may
+    shine through.  For example if Jinja2 introduces a new AST node in later
+    versions that may be returned by :meth:`~Environment.parse`.
index f6ec78c5c4c23a62979f0605ad9716b6daf8296b..bb0ca9fce9ce5a8081eea7a121cbd8aae9531b72 100644 (file)
@@ -29,3 +29,18 @@ SecurityError: access to attribute 'func_code' of 'function' object is unsafe.
 .. autofunction:: is_internal_attribute
 
 .. autofunction:: modifies_known_mutable
+
+.. admonition:: Note
+
+    The Jinja2 sandbox alone is no solution for perfect security.  Especially
+    for web applications you have to keep in mind that users may create
+    templates with arbitrary HTML in so it's crucial to ensure that (if you
+    are running multiple users on the same server) they can't harm each other
+    via JavaScript insertions and much more.
+
+    Also the sandbox is only as good as the configuration.  We stronly
+    recommend only passing non-shared resources to the template and use
+    some sort of whitelisting for attributes.
+
+    Also keep in mind that templates may raise runtime or compile time errors,
+    so make sure to catch them.
index f521f0826651bffc33ac3d86983d119700ce3385..0c496317ca99f1addea3cf7ae1e47fe0661f6af6 100644 (file)
@@ -174,6 +174,13 @@ operator.  Here are some examples::
         hmm. {{ user.username|e }} looks pretty normal
     {% endif %}
 
+Loops
+~~~~~
+
+For loops work very similar to Django, the only incompatibility is that in
+Jinja2 the special variable for the loop context is called `loop` and not
+`forloop` like in Django.
+
 
 Mako
 ----
index ca307218662082551497e0ba1066e94a9a4105ea..182ef9468b7b33e1c2e3375499c3060e3ef27b56 100644 (file)
@@ -147,19 +147,21 @@ that block::
 This will yield all elements without whitespace between them.  If `seq` was
 a list of numbers from ``1`` to ``9`` the output would be ``123456789``.
 
-Note that you must not use a whitespace between the tag and the minus sign:
+If :ref:`line-statements` are enabled they strip leading whitespace
+automatically up to the beginning of the line.
+
+.. admonition:: Note
 
-    valid::
+    You must not use a whitespace between the tag and the minus sign.
+
+    **valid**::
 
         {%- if foo -%}...{% endif %}
 
-    invalid::
+    **invalid**::
 
         {% - if foo - %}...{% endif %}
 
-If :ref:`line-statements` are enabled they strip leading whitespace
-automatically up to the beginning of the line.
-
 
 Escaping
 --------
@@ -797,8 +799,16 @@ for Python objects such as strings and numbers.  The following literals exist:
     filter.
 
 true / false:
-    true is always true and false is always false.  Keep in mind that those
-    literals are lowercase!
+    true is always true and false is always false.
+
+.. admonition:: Note
+
+    The special constants `true`, `false` and `none` are indeed lowercase.
+    Because that caused confusion in the past, when writing `True` expands
+    to an undefined variable that is considered false, all three of them can
+    be written in title case too (`True`, `False`, and `None`).  However for
+    consistency (all Jinja identifiers are lowercase) you should use the
+    lowercase versions.
 
 Math
 ~~~~
@@ -854,12 +864,12 @@ not
 (expr)
     group an expression.
 
-Note that there is no support for any bit operations or something similar.
+.. admonition:: Note
 
--   special note regarding ``not``: The ``is`` and ``in`` operators support
-    negation using an infix notation too: ``foo is not bar`` and
-    ``foo not in bar`` instead of ``not foo is bar`` and ``not foo in bar``.
-    All other expressions require a prefix notation: ``not (foo and bar).``
+    The ``is`` and ``in`` operators support negation using an infix notation
+    too: ``foo is not bar`` and ``foo not in bar`` instead of ``not foo is bar``
+    and ``not foo in bar``.  All other expressions require a prefix notation:
+    ``not (foo and bar).``
 
 
 Other Operators
index a224c3020e53e97ffbd4b75681576e137f7d51d6..bff9e8306f196034b5cd6018e4a4017ad5fb6844 100644 (file)
    '("\\({{ ?\\)\\([^|]*?\\)\\(|.*?\\)? ?}}" . (1 font-lock-variable-name-face))
    ;; keywords and builtins
    (cons (rx word-start
-             (or "in" "as" "reversed" "recursive" "not" "and" "or"
-                 "if" "else" "import" "with" "without" "context")
+             (or "in" "as" "recursive" "not" "and" "or" "if" "else"
+                 "import" "with" "without" "context")
              word-end)
          font-lock-keyword-face)
    (cons (rx word-start
-             (or "true" "false" "null" "loop" "block" "super" "forloop")
+             (or "true" "false" "none" "loop" "self" "super")
              word-end)
          font-lock-builtin-face)
    ;; tests
index cd720a6248ce363de7a5ada0e21aa4922c50018b..194390a9e9756706acb79a3eb1e25da72442bf76 100644 (file)
@@ -50,7 +50,7 @@ from jinja2.exceptions import TemplateError, UndefinedError, \
 # decorators and public utilities
 from jinja2.filters import environmentfilter, contextfilter
 from jinja2.utils import Markup, escape, clear_caches, \
-     environmentfunction, contextfunction
+     environmentfunction, contextfunction, is_undefined
 
 __all__ = [
     'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
@@ -59,5 +59,5 @@ __all__ = [
     'TemplateError', 'UndefinedError', 'TemplateNotFound',
     'TemplateSyntaxError', 'TemplateAssertionError', 'environmentfilter',
     'contextfilter', 'Markup', 'escape', 'environmentfunction',
-    'contextfunction', 'clear_caches'
+    'contextfunction', 'clear_caches', 'is_undefined'
 ]
index f691c780290aa9f1ecb8a396d05d2d58ac4898a7..8a9a1085b753d8c1abc10fadc8e884ceccd2891b 100644 (file)
@@ -187,7 +187,7 @@ tb_set_next(PyObject *self, PyObject *args)
 static PyMethodDef module_methods[] = {
        {"escape", (PyCFunction)escape, METH_O,
         "escape(s) -> markup\n\n"
-        "Convert the characters &, <, >, and \" in string s to HTML-safe\n"
+        "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n"
         "sequences.  Use this if you need to display text that might contain\n"
         "such characters in HTML.  Marks return value as markup string."},
        {"soft_unicode", (PyCFunction)soft_unicode, METH_O,
index 0a32f9ae30640874d92122996494bf6b385f571b..762b06dd2d83ca4a04beb33cf907ba6f6087f3a7 100644 (file)
@@ -23,8 +23,8 @@ _word_re = re.compile(r'\w+')
 
 
 def contextfilter(f):
-    """Decorator for marking context dependent filters. The current context
-    argument will be passed as first argument.
+    """Decorator for marking context dependent filters. The current
+    :class:`Context` will be passed as first argument.
     """
     if getattr(f, 'environmentfilter', False):
         raise TypeError('filter already marked as environment filter')
@@ -33,8 +33,8 @@ def contextfilter(f):
 
 
 def environmentfilter(f):
-    """Decorator for marking evironment dependent filters.  The environment
-    used for the template is passed to the filter as first argument.
+    """Decorator for marking evironment dependent filters.  The current
+    :class:`Environment` is passed to the filter as first argument.
     """
     if getattr(f, 'contextfilter', False):
         raise TypeError('filter already marked as context filter')
@@ -578,7 +578,8 @@ def do_groupby(environment, value, attribute):
 
 class _GroupTuple(tuple):
     __slots__ = ()
-    grouper, list = (property(itemgetter(x)) for x in xrange(2))
+    grouper = property(itemgetter(0))
+    list = property(itemgetter(1))
 
     def __new__(cls, (key, value)):
         return tuple.__new__(cls, (key, list(value)))
index 0cccddf81137a02b987d7e0ea4ed302d17afb5e8..f4b1f3232330d43aa85447a759b0a849dd6de6ee 100644 (file)
@@ -400,7 +400,8 @@ class Name(Expr):
     fields = ('name', 'ctx')
 
     def can_assign(self):
-        return self.name not in ('true', 'false', 'none')
+        return self.name not in ('true', 'false', 'none',
+                                 'True', 'False', 'None')
 
 
 class Literal(Expr):
index 0ce4a292cfc16ef39c61d0da55a3204b3bb1e320..7efe79cdd767c109eeee26c8b9a366a1c7329192 100644 (file)
@@ -454,9 +454,10 @@ class Parser(object):
     def parse_primary(self, with_postfix=True):
         token = self.stream.current
         if token.type is 'name':
-            if token.value in ('true', 'false'):
-                node = nodes.Const(token.value == 'true', lineno=token.lineno)
-            elif token.value == 'none':
+            if token.value in ('true', 'false', 'True', 'False'):
+                node = nodes.Const(token.value in ('true', 'True'),
+                                   lineno=token.lineno)
+            elif token.value in ('none', 'None'):
                 node = nodes.Const(None, lineno=token.lineno)
             else:
                 node = nodes.Name(token.value, 'load', lineno=token.lineno)
index babc3c93499bd1ae3750d9fb7cb5c103bd38a2f6..2dbd5693edd6d204110dd71ec131c41f2a1c6b3b 100644 (file)
@@ -117,6 +117,11 @@ class Context(object):
         return dict(self.parent, **self.vars)
 
     def call(__self, __obj, *args, **kwargs):
+        """Call the callable with the arguments and keyword arguments
+        provided but inject the active context or environment as first
+        argument if the callable is a :func:`contextfunction` or
+        :func:`environmentfunction`.
+        """
         if __debug__:
             __traceback_hide__ = True
         if isinstance(__obj, _context_function_types):
@@ -160,6 +165,14 @@ class Context(object):
         )
 
 
+# register the context as mutable mapping if possible
+try:
+    from collections import MutableMapping
+    MutableMapping.register(Context)
+except ImportError:
+    pass
+
+
 class TemplateReference(object):
     """The `self` in templates."""
 
@@ -321,28 +334,6 @@ class Macro(object):
         )
 
 
-def fail_with_undefined_error(self, *args, **kwargs):
-    """Regular callback function for undefined objects that raises an
-    `UndefinedError` on call.
-    """
-    if self._undefined_hint is None:
-        if self._undefined_obj is None:
-            hint = '%r is undefined' % self._undefined_name
-        elif not isinstance(self._undefined_name, basestring):
-            hint = '%r object has no element %r' % (
-                self._undefined_obj.__class__.__name__,
-                self._undefined_name
-            )
-        else:
-            hint = '%r object has no attribute %r' % (
-                self._undefined_obj.__class__.__name__,
-                self._undefined_name
-            )
-    else:
-        hint = self._undefined_hint
-    raise self._undefined_exception(hint)
-
-
 class Undefined(object):
     """The default undefined type.  This undefined type can be printed and
     iterated over, but every other access will raise an :exc:`UndefinedError`:
@@ -366,18 +357,36 @@ class Undefined(object):
         self._undefined_name = name
         self._undefined_exception = exc
 
+    def _fail_with_undefined_error(self, *args, **kwargs):
+        """Regular callback function for undefined objects that raises an
+        `UndefinedError` on call.
+        """
+        if self._undefined_hint is None:
+            if self._undefined_obj is None:
+                hint = '%r is undefined' % self._undefined_name
+            elif not isinstance(self._undefined_name, basestring):
+                hint = '%r object has no element %r' % (
+                    self._undefined_obj.__class__.__name__,
+                    self._undefined_name
+                )
+            else:
+                hint = '%r object has no attribute %r' % (
+                    self._undefined_obj.__class__.__name__,
+                    self._undefined_name
+                )
+        else:
+            hint = self._undefined_hint
+        raise self._undefined_exception(hint)
+
     __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
     __realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \
     __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
     __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \
-        fail_with_undefined_error
+    __int__ = __float__ = __complex__ = _fail_with_undefined_error
 
     def __str__(self):
         return self.__unicode__().encode('utf-8')
 
-    def __repr__(self):
-        return 'Undefined'
-
     def __unicode__(self):
         return u''
 
@@ -391,6 +400,9 @@ class Undefined(object):
     def __nonzero__(self):
         return False
 
+    def __repr__(self):
+        return 'Undefined'
+
 
 class DebugUndefined(Undefined):
     """An undefined that returns the debug info when printed.
@@ -439,7 +451,7 @@ class StrictUndefined(Undefined):
     """
     __slots__ = ()
     __iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \
-        fail_with_undefined_error
+        Undefined._fail_with_undefined_error
 
 
 # remove remaining slots attributes, after the metaclass did the magic they
index e064a259bf379f09279f89497eaedde26f635236..6d1c9580ac66f8aa18ac2d9c57ea66bd7ca66f74 100644 (file)
@@ -63,23 +63,47 @@ except TypeError, _error:
 
 
 def contextfunction(f):
-    """This decorator can be used to mark a callable as context callable.  A
-    context callable is passed the active context as first argument when
-    called from the template.
+    """This decorator can be used to mark a function or method context callable.
+    A context callable is passed the active :class:`Context` as first argument when
+    called from the template.  This is useful if a function wants to get access
+    to the context or functions provided on the context object.  For example
+    a function that returns a sorted list of template variables the current
+    template exports could look like this::
+
+        @contextcallable
+        def get_exported_names(context):
+            return sorted(context.exported_vars)
     """
     f.contextfunction = True
     return f
 
 
 def environmentfunction(f):
-    """This decorator can be used to mark a callable as environment callable.
-    A environment callable is passed the current environment as first argument
-    when called from the template.
+    """This decorator can be used to mark a function or method as environment
+    callable.  This decorator works exactly like the :func:`contextfunction`
+    decorator just that the first argument is the active :class:`Environment`
+    and not context.
     """
     f.environmentfunction = True
     return f
 
 
+def is_undefined(obj):
+    """Check if the object passed is undefined.  This does nothing more than
+    performing an instance check against :class:`Undefined` but looks nicer.
+    This can be used for custom filters or tests that want to react to
+    undefined variables.  For example a custom default filter can look like
+    this::
+
+        def default(var, default=''):
+            if is_undefined(var):
+                return default
+            return var
+    """
+    from jinja2.runtime import Undefined
+    return isinstance(obj, Undefined)
+
+
 def clear_caches():
     """Jinja2 keeps internal caches for environments and lexers.  These are
     used so that Jinja2 doesn't have to recreate environments and lexers all
@@ -532,6 +556,14 @@ class LRUCache(object):
     __copy__ = copy
 
 
+# register the LRU cache as mutable mapping if possible
+try:
+    from collections import MutableMapping
+    MutableMapping.register(LRUCache)
+except ImportError:
+    pass
+
+
 # we have to import it down here as the speedups module imports the
 # markup type which is define above.
 try:
index 717e1651930f4338f6fd5cb52464dc01d8da51c0..a126b5f18e0590de75359bfd5ebb7b5d6bfdb31c 100644 (file)
@@ -163,3 +163,11 @@ def test_trailing_comma(env):
 def test_block_end_name(env):
     env.from_string('{% block foo %}...{% endblock foo %}')
     raises(TemplateSyntaxError, env.from_string, '{% block x %}{% endblock y %}')
+
+
+def test_contant_casing(env):
+    for const in True, False, None:
+        tmpl = env.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % (
+            str(const), str(const).lower(), str(const).upper()
+        ))
+        assert tmpl.render() == '%s|%s|' % (const, const)