:license: BSD, see LICENSE for more details.
"""
__docformat__ = 'restructuredtext en'
-try:
- __version__ = __import__('pkg_resources') \
- .get_distribution('Jinja2').version
-except:
- __version__ = 'unknown'
+__version__ = '2.6'
# high level interface
from jinja2.environment import Environment, Template
# loaders
from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
- DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader
+ DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
+ ModuleLoader
# bytecode caches
from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
TemplateAssertionError
# decorators and public utilities
-from jinja2.filters import environmentfilter, contextfilter
+from jinja2.filters import environmentfilter, contextfilter, \
+ evalcontextfilter
from jinja2.utils import Markup, escape, clear_caches, \
- environmentfunction, contextfunction, is_undefined
+ environmentfunction, evalcontextfunction, contextfunction, \
+ is_undefined
__all__ = [
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
- 'environmentfilter', 'contextfilter', 'Markup', 'escape',
- 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined'
+ 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
+ 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
+ 'evalcontextfilter', 'evalcontextfunction'
]
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ markupsafe
+ ~~~~~~~~~~
+
+ Implements a Markup string.
+
+ :copyright: (c) 2010 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+from itertools import imap
+
+
+__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
+
+
+_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
+_entity_re = re.compile(r'&([^;]+);')
+
+
+class Markup(unicode):
+ r"""Marks a string as being safe for inclusion in HTML/XML output without
+ needing to be escaped. This implements the `__html__` interface a couple
+ of frameworks and web applications use. :class:`Markup` is a direct
+ subclass of `unicode` and provides all the methods of `unicode` just that
+ it escapes arguments passed and always returns `Markup`.
+
+ The `escape` function returns markup objects so that double escaping can't
+ happen.
+
+ The constructor of the :class:`Markup` class can be used for three
+ different things: When passed an unicode object it's assumed to be safe,
+ when passed an object with an HTML representation (has an `__html__`
+ method) that representation is used, otherwise the object passed is
+ converted into a unicode string and then assumed to be safe:
+
+ >>> Markup("Hello <em>World</em>!")
+ Markup(u'Hello <em>World</em>!')
+ >>> class Foo(object):
+ ... def __html__(self):
+ ... return '<a href="#">foo</a>'
+ ...
+ >>> Markup(Foo())
+ Markup(u'<a href="#">foo</a>')
+
+ If you want object passed being always treated as unsafe you can use the
+ :meth:`escape` classmethod to create a :class:`Markup` object:
+
+ >>> Markup.escape("Hello <em>World</em>!")
+ Markup(u'Hello <em>World</em>!')
+
+ Operations on a markup string are markup aware which means that all
+ arguments are passed through the :func:`escape` function:
+
+ >>> em = Markup("<em>%s</em>")
+ >>> em % "foo & bar"
+ Markup(u'<em>foo & bar</em>')
+ >>> strong = Markup("<strong>%(text)s</strong>")
+ >>> strong % {'text': '<blink>hacker here</blink>'}
+ Markup(u'<strong><blink>hacker here</blink></strong>')
+ >>> Markup("<em>Hello</em> ") + "<foo>"
+ Markup(u'<em>Hello</em> <foo>')
+ """
+ __slots__ = ()
+
+ def __new__(cls, base=u'', encoding=None, errors='strict'):
+ if hasattr(base, '__html__'):
+ base = base.__html__()
+ if encoding is None:
+ return unicode.__new__(cls, base)
+ return unicode.__new__(cls, base, encoding, errors)
+
+ def __html__(self):
+ return self
+
+ def __add__(self, other):
+ if hasattr(other, '__html__') or isinstance(other, basestring):
+ return self.__class__(unicode(self) + unicode(escape(other)))
+ return NotImplemented
+
+ def __radd__(self, other):
+ if hasattr(other, '__html__') or isinstance(other, basestring):
+ return self.__class__(unicode(escape(other)) + unicode(self))
+ return NotImplemented
+
+ def __mul__(self, num):
+ if isinstance(num, (int, long)):
+ return self.__class__(unicode.__mul__(self, num))
+ return NotImplemented
+ __rmul__ = __mul__
+
+ def __mod__(self, arg):
+ if isinstance(arg, tuple):
+ arg = tuple(imap(_MarkupEscapeHelper, arg))
+ else:
+ arg = _MarkupEscapeHelper(arg)
+ return self.__class__(unicode.__mod__(self, arg))
+
+ def __repr__(self):
+ return '%s(%s)' % (
+ self.__class__.__name__,
+ unicode.__repr__(self)
+ )
+
+ def join(self, seq):
+ return self.__class__(unicode.join(self, imap(escape, seq)))
+ join.__doc__ = unicode.join.__doc__
+
+ def split(self, *args, **kwargs):
+ return map(self.__class__, unicode.split(self, *args, **kwargs))
+ split.__doc__ = unicode.split.__doc__
+
+ def rsplit(self, *args, **kwargs):
+ return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
+ rsplit.__doc__ = unicode.rsplit.__doc__
+
+ def splitlines(self, *args, **kwargs):
+ return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
+ splitlines.__doc__ = unicode.splitlines.__doc__
+
+ def unescape(self):
+ r"""Unescape markup again into an unicode string. This also resolves
+ known HTML4 and XHTML entities:
+
+ >>> Markup("Main » <em>About</em>").unescape()
+ u'Main \xbb <em>About</em>'
+ """
+ from jinja2._markupsafe._constants import HTML_ENTITIES
+ def handle_match(m):
+ name = m.group(1)
+ if name in HTML_ENTITIES:
+ return unichr(HTML_ENTITIES[name])
+ try:
+ if name[:2] in ('#x', '#X'):
+ return unichr(int(name[2:], 16))
+ elif name.startswith('#'):
+ return unichr(int(name[1:]))
+ except ValueError:
+ pass
+ return u''
+ return _entity_re.sub(handle_match, unicode(self))
+
+ def striptags(self):
+ r"""Unescape markup into an unicode string and strip all tags. This
+ also resolves known HTML4 and XHTML entities. Whitespace is
+ normalized to one:
+
+ >>> Markup("Main » <em>About</em>").striptags()
+ u'Main \xbb About'
+ """
+ stripped = u' '.join(_striptags_re.sub('', self).split())
+ return Markup(stripped).unescape()
+
+ @classmethod
+ def escape(cls, s):
+ """Escape the string. Works like :func:`escape` with the difference
+ that for subclasses of :class:`Markup` this function would return the
+ correct subclass.
+ """
+ rv = escape(s)
+ if rv.__class__ is not cls:
+ return cls(rv)
+ return rv
+
+ def make_wrapper(name):
+ orig = getattr(unicode, name)
+ def func(self, *args, **kwargs):
+ args = _escape_argspec(list(args), enumerate(args))
+ _escape_argspec(kwargs, kwargs.iteritems())
+ return self.__class__(orig(self, *args, **kwargs))
+ func.__name__ = orig.__name__
+ func.__doc__ = orig.__doc__
+ return func
+
+ for method in '__getitem__', 'capitalize', \
+ 'title', 'lower', 'upper', 'replace', 'ljust', \
+ 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
+ 'translate', 'expandtabs', 'swapcase', 'zfill':
+ locals()[method] = make_wrapper(method)
+
+ # new in python 2.5
+ if hasattr(unicode, 'partition'):
+ partition = make_wrapper('partition'),
+ rpartition = make_wrapper('rpartition')
+
+ # new in python 2.6
+ if hasattr(unicode, 'format'):
+ format = make_wrapper('format')
+
+ # not in python 3
+ if hasattr(unicode, '__getslice__'):
+ __getslice__ = make_wrapper('__getslice__')
+
+ del method, make_wrapper
+
+
+def _escape_argspec(obj, iterable):
+ """Helper for various string-wrapped functions."""
+ for key, value in iterable:
+ if hasattr(value, '__html__') or isinstance(value, basestring):
+ obj[key] = escape(value)
+ return obj
+
+
+class _MarkupEscapeHelper(object):
+ """Helper for Markup.__mod__"""
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
+ __str__ = lambda s: str(escape(s.obj))
+ __unicode__ = lambda s: unicode(escape(s.obj))
+ __repr__ = lambda s: str(escape(repr(s.obj)))
+ __int__ = lambda s: int(s.obj)
+ __float__ = lambda s: float(s.obj)
+
+
+# we have to import it down here as the speedups and native
+# modules imports the markup type which is define above.
+try:
+ from jinja2._markupsafe._speedups import escape, escape_silent, soft_unicode
+except ImportError:
+ from jinja2._markupsafe._native import escape, escape_silent, soft_unicode
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja2._markupsafe._bundle
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ This script pulls in markupsafe from a source folder and
+ bundles it with Jinja2. It does not pull in the speedups
+ module though.
+
+ :copyright: Copyright 2010 by the Jinja team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+import sys
+import os
+import re
+
+
+def rewrite_imports(lines):
+ for idx, line in enumerate(lines):
+ new_line = re.sub(r'(import|from)\s+markupsafe\b',
+ r'\1 jinja2._markupsafe', line)
+ if new_line != line:
+ lines[idx] = new_line
+
+
+def main():
+ if len(sys.argv) != 2:
+ print 'error: only argument is path to markupsafe'
+ sys.exit(1)
+ basedir = os.path.dirname(__file__)
+ markupdir = sys.argv[1]
+ for filename in os.listdir(markupdir):
+ if filename.endswith('.py'):
+ f = open(os.path.join(markupdir, filename))
+ try:
+ lines = list(f)
+ finally:
+ f.close()
+ rewrite_imports(lines)
+ f = open(os.path.join(basedir, filename), 'w')
+ try:
+ for line in lines:
+ f.write(line)
+ finally:
+ f.close()
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ markupsafe._constants
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ Highlevel implementation of the Markup string.
+
+ :copyright: (c) 2010 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+HTML_ENTITIES = {
+ 'AElig': 198,
+ 'Aacute': 193,
+ 'Acirc': 194,
+ 'Agrave': 192,
+ 'Alpha': 913,
+ 'Aring': 197,
+ 'Atilde': 195,
+ 'Auml': 196,
+ 'Beta': 914,
+ 'Ccedil': 199,
+ 'Chi': 935,
+ 'Dagger': 8225,
+ 'Delta': 916,
+ 'ETH': 208,
+ 'Eacute': 201,
+ 'Ecirc': 202,
+ 'Egrave': 200,
+ 'Epsilon': 917,
+ 'Eta': 919,
+ 'Euml': 203,
+ 'Gamma': 915,
+ 'Iacute': 205,
+ 'Icirc': 206,
+ 'Igrave': 204,
+ 'Iota': 921,
+ 'Iuml': 207,
+ 'Kappa': 922,
+ 'Lambda': 923,
+ 'Mu': 924,
+ 'Ntilde': 209,
+ 'Nu': 925,
+ 'OElig': 338,
+ 'Oacute': 211,
+ 'Ocirc': 212,
+ 'Ograve': 210,
+ 'Omega': 937,
+ 'Omicron': 927,
+ 'Oslash': 216,
+ 'Otilde': 213,
+ 'Ouml': 214,
+ 'Phi': 934,
+ 'Pi': 928,
+ 'Prime': 8243,
+ 'Psi': 936,
+ 'Rho': 929,
+ 'Scaron': 352,
+ 'Sigma': 931,
+ 'THORN': 222,
+ 'Tau': 932,
+ 'Theta': 920,
+ 'Uacute': 218,
+ 'Ucirc': 219,
+ 'Ugrave': 217,
+ 'Upsilon': 933,
+ 'Uuml': 220,
+ 'Xi': 926,
+ 'Yacute': 221,
+ 'Yuml': 376,
+ 'Zeta': 918,
+ 'aacute': 225,
+ 'acirc': 226,
+ 'acute': 180,
+ 'aelig': 230,
+ 'agrave': 224,
+ 'alefsym': 8501,
+ 'alpha': 945,
+ 'amp': 38,
+ 'and': 8743,
+ 'ang': 8736,
+ 'apos': 39,
+ 'aring': 229,
+ 'asymp': 8776,
+ 'atilde': 227,
+ 'auml': 228,
+ 'bdquo': 8222,
+ 'beta': 946,
+ 'brvbar': 166,
+ 'bull': 8226,
+ 'cap': 8745,
+ 'ccedil': 231,
+ 'cedil': 184,
+ 'cent': 162,
+ 'chi': 967,
+ 'circ': 710,
+ 'clubs': 9827,
+ 'cong': 8773,
+ 'copy': 169,
+ 'crarr': 8629,
+ 'cup': 8746,
+ 'curren': 164,
+ 'dArr': 8659,
+ 'dagger': 8224,
+ 'darr': 8595,
+ 'deg': 176,
+ 'delta': 948,
+ 'diams': 9830,
+ 'divide': 247,
+ 'eacute': 233,
+ 'ecirc': 234,
+ 'egrave': 232,
+ 'empty': 8709,
+ 'emsp': 8195,
+ 'ensp': 8194,
+ 'epsilon': 949,
+ 'equiv': 8801,
+ 'eta': 951,
+ 'eth': 240,
+ 'euml': 235,
+ 'euro': 8364,
+ 'exist': 8707,
+ 'fnof': 402,
+ 'forall': 8704,
+ 'frac12': 189,
+ 'frac14': 188,
+ 'frac34': 190,
+ 'frasl': 8260,
+ 'gamma': 947,
+ 'ge': 8805,
+ 'gt': 62,
+ 'hArr': 8660,
+ 'harr': 8596,
+ 'hearts': 9829,
+ 'hellip': 8230,
+ 'iacute': 237,
+ 'icirc': 238,
+ 'iexcl': 161,
+ 'igrave': 236,
+ 'image': 8465,
+ 'infin': 8734,
+ 'int': 8747,
+ 'iota': 953,
+ 'iquest': 191,
+ 'isin': 8712,
+ 'iuml': 239,
+ 'kappa': 954,
+ 'lArr': 8656,
+ 'lambda': 955,
+ 'lang': 9001,
+ 'laquo': 171,
+ 'larr': 8592,
+ 'lceil': 8968,
+ 'ldquo': 8220,
+ 'le': 8804,
+ 'lfloor': 8970,
+ 'lowast': 8727,
+ 'loz': 9674,
+ 'lrm': 8206,
+ 'lsaquo': 8249,
+ 'lsquo': 8216,
+ 'lt': 60,
+ 'macr': 175,
+ 'mdash': 8212,
+ 'micro': 181,
+ 'middot': 183,
+ 'minus': 8722,
+ 'mu': 956,
+ 'nabla': 8711,
+ 'nbsp': 160,
+ 'ndash': 8211,
+ 'ne': 8800,
+ 'ni': 8715,
+ 'not': 172,
+ 'notin': 8713,
+ 'nsub': 8836,
+ 'ntilde': 241,
+ 'nu': 957,
+ 'oacute': 243,
+ 'ocirc': 244,
+ 'oelig': 339,
+ 'ograve': 242,
+ 'oline': 8254,
+ 'omega': 969,
+ 'omicron': 959,
+ 'oplus': 8853,
+ 'or': 8744,
+ 'ordf': 170,
+ 'ordm': 186,
+ 'oslash': 248,
+ 'otilde': 245,
+ 'otimes': 8855,
+ 'ouml': 246,
+ 'para': 182,
+ 'part': 8706,
+ 'permil': 8240,
+ 'perp': 8869,
+ 'phi': 966,
+ 'pi': 960,
+ 'piv': 982,
+ 'plusmn': 177,
+ 'pound': 163,
+ 'prime': 8242,
+ 'prod': 8719,
+ 'prop': 8733,
+ 'psi': 968,
+ 'quot': 34,
+ 'rArr': 8658,
+ 'radic': 8730,
+ 'rang': 9002,
+ 'raquo': 187,
+ 'rarr': 8594,
+ 'rceil': 8969,
+ 'rdquo': 8221,
+ 'real': 8476,
+ 'reg': 174,
+ 'rfloor': 8971,
+ 'rho': 961,
+ 'rlm': 8207,
+ 'rsaquo': 8250,
+ 'rsquo': 8217,
+ 'sbquo': 8218,
+ 'scaron': 353,
+ 'sdot': 8901,
+ 'sect': 167,
+ 'shy': 173,
+ 'sigma': 963,
+ 'sigmaf': 962,
+ 'sim': 8764,
+ 'spades': 9824,
+ 'sub': 8834,
+ 'sube': 8838,
+ 'sum': 8721,
+ 'sup': 8835,
+ 'sup1': 185,
+ 'sup2': 178,
+ 'sup3': 179,
+ 'supe': 8839,
+ 'szlig': 223,
+ 'tau': 964,
+ 'there4': 8756,
+ 'theta': 952,
+ 'thetasym': 977,
+ 'thinsp': 8201,
+ 'thorn': 254,
+ 'tilde': 732,
+ 'times': 215,
+ 'trade': 8482,
+ 'uArr': 8657,
+ 'uacute': 250,
+ 'uarr': 8593,
+ 'ucirc': 251,
+ 'ugrave': 249,
+ 'uml': 168,
+ 'upsih': 978,
+ 'upsilon': 965,
+ 'uuml': 252,
+ 'weierp': 8472,
+ 'xi': 958,
+ 'yacute': 253,
+ 'yen': 165,
+ 'yuml': 255,
+ 'zeta': 950,
+ 'zwj': 8205,
+ 'zwnj': 8204
+}
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ markupsafe._native
+ ~~~~~~~~~~~~~~~~~~
+
+ Native Python implementation the C module is not compiled.
+
+ :copyright: (c) 2010 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja2._markupsafe import Markup
+
+
+def 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. Marks return value as markup string.
+ """
+ if hasattr(s, '__html__'):
+ return s.__html__()
+ return Markup(unicode(s)
+ .replace('&', '&')
+ .replace('>', '>')
+ .replace('<', '<')
+ .replace("'", ''')
+ .replace('"', '"')
+ )
+
+
+def escape_silent(s):
+ """Like :func:`escape` but converts `None` into an empty
+ markup string.
+ """
+ if s is None:
+ return Markup()
+ return escape(s)
+
+
+def soft_unicode(s):
+ """Make a string unicode if it isn't already. That way a markup
+ string is not converted back to unicode.
+ """
+ if not isinstance(s, unicode):
+ s = unicode(s)
+ return s
--- /dev/null
+import gc
+import unittest
+from jinja2._markupsafe import Markup, escape, escape_silent
+
+
+class MarkupTestCase(unittest.TestCase):
+
+ def test_markup_operations(self):
+ # adding two strings should escape the unsafe one
+ unsafe = '<script type="application/x-some-script">alert("foo");</script>'
+ safe = Markup('<em>username</em>')
+ assert unsafe + safe == unicode(escape(unsafe)) + unicode(safe)
+
+ # string interpolations are safe to use too
+ assert Markup('<em>%s</em>') % '<bad user>' == \
+ '<em><bad user></em>'
+ assert Markup('<em>%(username)s</em>') % {
+ 'username': '<bad user>'
+ } == '<em><bad user></em>'
+
+ # an escaped object is markup too
+ assert type(Markup('foo') + 'bar') is Markup
+
+ # and it implements __html__ by returning itself
+ x = Markup("foo")
+ assert x.__html__() is x
+
+ # it also knows how to treat __html__ objects
+ class Foo(object):
+ def __html__(self):
+ return '<em>awesome</em>'
+ def __unicode__(self):
+ return 'awesome'
+ assert Markup(Foo()) == '<em>awesome</em>'
+ assert Markup('<strong>%s</strong>') % Foo() == \
+ '<strong><em>awesome</em></strong>'
+
+ # escaping and unescaping
+ assert escape('"<>&\'') == '"<>&''
+ assert Markup("<em>Foo & Bar</em>").striptags() == "Foo & Bar"
+ assert Markup("<test>").unescape() == "<test>"
+
+ def test_all_set(self):
+ import jinja2._markupsafe as markup
+ for item in markup.__all__:
+ getattr(markup, item)
+
+ def test_escape_silent(self):
+ assert escape_silent(None) == Markup()
+ assert escape(None) == Markup(None)
+ assert escape_silent('<foo>') == Markup(u'<foo>')
+
+
+class MarkupLeakTestCase(unittest.TestCase):
+
+ def test_markup_leaks(self):
+ counts = set()
+ for count in xrange(20):
+ for item in xrange(1000):
+ escape("foo")
+ escape("<foo>")
+ escape(u"foo")
+ escape(u"<foo>")
+ counts.add(len(gc.get_objects()))
+ assert len(counts) == 1, 'ouch, c extension seems to leak objects'
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(MarkupTestCase))
+
+ # this test only tests the c extension
+ if not hasattr(escape, 'func_code'):
+ suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
+
+ return suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
+++ /dev/null
-/**
- * jinja2._speedups
- * ~~~~~~~~~~~~~~~~
- *
- * This module implements functions for automatic escaping in C for better
- * performance. Additionally it defines a `tb_set_next` function to patch the
- * debug traceback. If the speedups module is not compiled a ctypes
- * implementation of `tb_set_next` and Python implementations of the other
- * functions are used.
- *
- * :copyright: (c) 2009 by the Jinja Team.
- * :license: BSD.
- */
-
-#include <Python.h>
-
-#define ESCAPED_CHARS_TABLE_SIZE 63
-#define UNICHR(x) (((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL))->str);
-
-#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
-typedef int Py_ssize_t;
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
-#endif
-
-
-static PyObject* markup;
-static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
-static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
-
-static int
-init_constants(void)
-{
- PyObject *module;
- /* happing of characters to replace */
- escaped_chars_repl['"'] = UNICHR(""");
- escaped_chars_repl['\''] = UNICHR("'");
- escaped_chars_repl['&'] = UNICHR("&");
- escaped_chars_repl['<'] = UNICHR("<");
- escaped_chars_repl['>'] = UNICHR(">");
-
- /* lengths of those characters when replaced - 1 */
- memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len));
- escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
- escaped_chars_delta_len['&'] = 4;
- escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
-
- /* import markup type so that we can mark the return value */
- module = PyImport_ImportModule("jinja2.utils");
- if (!module)
- return 0;
- markup = PyObject_GetAttrString(module, "Markup");
- Py_DECREF(module);
-
- return 1;
-}
-
-static PyObject*
-escape_unicode(PyUnicodeObject *in)
-{
- PyUnicodeObject *out;
- Py_UNICODE *inp = in->str;
- const Py_UNICODE *inp_end = in->str + in->length;
- Py_UNICODE *next_escp;
- Py_UNICODE *outp;
- Py_ssize_t delta=0, erepl=0, delta_len=0;
-
- /* First we need to figure out how long the escaped string will be */
- while (*(inp) || inp < inp_end) {
- if (*inp < ESCAPED_CHARS_TABLE_SIZE && escaped_chars_delta_len[*inp]) {
- delta += escaped_chars_delta_len[*inp];
- ++erepl;
- }
- ++inp;
- }
-
- /* Do we need to escape anything at all? */
- if (!erepl) {
- Py_INCREF(in);
- return (PyObject*)in;
- }
-
- out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, in->length + delta);
- if (!out)
- return NULL;
-
- outp = out->str;
- inp = in->str;
- while (erepl-- > 0) {
- /* look for the next substitution */
- next_escp = inp;
- while (next_escp < inp_end) {
- if (*next_escp < ESCAPED_CHARS_TABLE_SIZE &&
- (delta_len = escaped_chars_delta_len[*next_escp])) {
- ++delta_len;
- break;
- }
- ++next_escp;
- }
-
- if (next_escp > inp) {
- /* copy unescaped chars between inp and next_escp */
- Py_UNICODE_COPY(outp, inp, next_escp-inp);
- outp += next_escp - inp;
- }
-
- /* escape 'next_escp' */
- Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len);
- outp += delta_len;
-
- inp = next_escp + 1;
- }
- if (inp < inp_end)
- Py_UNICODE_COPY(outp, inp, in->length - (inp - in->str));
-
- return (PyObject*)out;
-}
-
-
-static PyObject*
-escape(PyObject *self, PyObject *text)
-{
- PyObject *s = NULL, *rv = NULL, *html;
-
- /* we don't have to escape integers, bools or floats */
- if (PyInt_CheckExact(text) || PyLong_CheckExact(text) ||
- PyFloat_CheckExact(text) || PyBool_Check(text) ||
- text == Py_None)
- return PyObject_CallFunctionObjArgs(markup, text, NULL);
-
- /* if the object has an __html__ method that performs the escaping */
- html = PyObject_GetAttrString(text, "__html__");
- if (html) {
- rv = PyObject_CallObject(html, NULL);
- Py_DECREF(html);
- return rv;
- }
-
- /* otherwise make the object unicode if it isn't, then escape */
- PyErr_Clear();
- if (!PyUnicode_Check(text)) {
- PyObject *unicode = PyObject_Unicode(text);
- if (!unicode)
- return NULL;
- s = escape_unicode((PyUnicodeObject*)unicode);
- Py_DECREF(unicode);
- }
- else
- s = escape_unicode((PyUnicodeObject*)text);
-
- /* convert the unicode string into a markup object. */
- rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
- Py_DECREF(s);
- return rv;
-}
-
-
-static PyObject*
-soft_unicode(PyObject *self, PyObject *s)
-{
- if (!PyUnicode_Check(s))
- return PyObject_Unicode(s);
- Py_INCREF(s);
- return s;
-}
-
-
-static PyObject*
-tb_set_next(PyObject *self, PyObject *args)
-{
- PyTracebackObject *tb, *old;
- PyObject *next;
-
- if (!PyArg_ParseTuple(args, "O!O:tb_set_next", &PyTraceBack_Type, &tb, &next))
- return NULL;
- if (next == Py_None)
- next = NULL;
- else if (!PyTraceBack_Check(next)) {
- PyErr_SetString(PyExc_TypeError,
- "tb_set_next arg 2 must be traceback or None");
- return NULL;
- }
- else
- Py_INCREF(next);
-
- old = tb->tb_next;
- tb->tb_next = (PyTracebackObject*)next;
- Py_XDECREF(old);
-
- Py_INCREF(Py_None);
- return Py_None;
-}
-
-
-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"
- "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,
- "soft_unicode(object) -> string\n\n"
- "Make a string unicode if it isn't already. That way a markup\n"
- "string is not converted back to unicode."},
- {"tb_set_next", (PyCFunction)tb_set_next, METH_VARARGS,
- "Set the tb_next member of a traceback object."},
- {NULL, NULL, 0, NULL} /* Sentinel */
-};
-
-
-#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
-#define PyMODINIT_FUNC void
-#endif
-PyMODINIT_FUNC
-init_speedups(void)
-{
- if (!init_constants())
- return;
-
- Py_InitModule3("jinja2._speedups", module_methods, "");
-}
:license: BSD.
"""
from os import path, listdir
+import sys
import marshal
import tempfile
import cPickle as pickle
import fnmatch
-from cStringIO import StringIO
try:
from hashlib import sha1
except ImportError:
from jinja2.utils import open_if_exists
-bc_version = 1
-bc_magic = 'j2'.encode('ascii') + pickle.dumps(bc_version, 2)
+# marshal works better on 3.x, one hack less required
+if sys.version_info > (3, 0):
+ from io import BytesIO
+ marshal_dump = marshal.dump
+ marshal_load = marshal.load
+else:
+ from cStringIO import StringIO as BytesIO
+
+ def marshal_dump(code, f):
+ if isinstance(f, file):
+ marshal.dump(code, f)
+ else:
+ f.write(marshal.dumps(code))
+
+ def marshal_load(f):
+ if isinstance(f, file):
+ return marshal.load(f)
+ return marshal.loads(f.read())
+
+
+bc_version = 2
+
+# magic version used to only change with new jinja versions. With 2.6
+# we change this to also take Python version changes into account. The
+# reason for this is that Python tends to segfault if fed earlier bytecode
+# versions because someone thought it would be a good idea to reuse opcodes
+# or make Python incompatible with earlier versions.
+bc_magic = 'j2'.encode('ascii') + \
+ pickle.dumps(bc_version, 2) + \
+ pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1])
class Bucket(object):
if self.checksum != checksum:
self.reset()
return
- # now load the code. Because marshal is not able to load
- # from arbitrary streams we have to work around that
- if isinstance(f, file):
- self.code = marshal.load(f)
- else:
- self.code = marshal.loads(f.read())
+ self.code = marshal_load(f)
def write_bytecode(self, f):
"""Dump the bytecode into the file or file like object passed."""
raise TypeError('can\'t write empty bucket')
f.write(bc_magic)
pickle.dump(self.checksum, f, 2)
- if isinstance(f, file):
- marshal.dump(self.code, f)
- else:
- f.write(marshal.dumps(self.code))
+ marshal_dump(self.code, f)
def bytecode_from_string(self, string):
"""Load bytecode from a string."""
- self.load_bytecode(StringIO(string))
+ self.load_bytecode(BytesIO(string))
def bytecode_to_string(self):
"""Return the bytecode as string."""
- out = StringIO()
+ out = BytesIO()
self.write_bytecode(out)
return out.getvalue()
"""Returns the unique hash key for this template name."""
hash = sha1(name.encode('utf-8'))
if filename is not None:
+ filename = '|' + filename
if isinstance(filename, unicode):
filename = filename.encode('utf-8')
- hash.update('|' + filename)
+ hash.update(filename)
return hash.hexdigest()
def get_source_checksum(self, source):
from itertools import chain
from copy import deepcopy
from jinja2 import nodes
-from jinja2.visitor import NodeVisitor, NodeTransformer
+from jinja2.nodes import EvalContext
+from jinja2.visitor import NodeVisitor
from jinja2.exceptions import TemplateAssertionError
from jinja2.utils import Markup, concat, escape, is_python_keyword, next
unoptimize_before_dead_code = bool(unoptimize_before_dead_code().func_closure)
-def generate(node, environment, name, filename, stream=None):
+def generate(node, environment, name, filename, stream=None,
+ defer_init=False):
"""Generate the python source for a node tree."""
if not isinstance(node, nodes.Template):
raise TypeError('Can\'t compile non template nodes')
- generator = CodeGenerator(environment, name, filename, stream)
+ generator = CodeGenerator(environment, name, filename, stream, defer_init)
generator.visit(node)
if stream is None:
return generator.stream.getvalue()
self.undeclared.discard(name)
self.declared.add(name)
- def is_declared(self, name, local_only=False):
+ def is_declared(self, name):
"""Check if a name is declared in this or an outer scope."""
if name in self.declared_locally or name in self.declared_parameter:
return True
- if local_only:
- return False
return name in self.declared
def copy(self):
class Frame(object):
"""Holds compile time information for us."""
- def __init__(self, parent=None):
+ def __init__(self, eval_ctx, parent=None):
+ self.eval_ctx = eval_ctx
self.identifiers = Identifiers()
# a toplevel frame is the root + soft frames such as if conditions.
rv.identifiers.__dict__.update(self.identifiers.__dict__)
return rv
- def inspect(self, nodes, hard_scope=False):
+ def inspect(self, nodes):
"""Walk the node and check for identifiers. If the scope is hard (eg:
enforce on a python level) overrides from outer scopes are tracked
differently.
"""
- visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
+ visitor = FrameIdentifierVisitor(self.identifiers)
for node in nodes:
visitor.visit(node)
def inner(self):
"""Return an inner frame."""
- return Frame(self)
+ return Frame(self.eval_ctx, self)
def soft(self):
"""Return a soft frame. A soft frame may not be modified as
class FrameIdentifierVisitor(NodeVisitor):
"""A visitor for `Frame.inspect`."""
- def __init__(self, identifiers, hard_scope):
+ def __init__(self, identifiers):
self.identifiers = identifiers
- self.hard_scope = hard_scope
def visit_Name(self, node):
"""All assignments to names go through this function."""
elif node.ctx == 'param':
self.identifiers.declared_parameter.add(node.name)
elif node.ctx == 'load' and not \
- self.identifiers.is_declared(node.name, self.hard_scope):
+ self.identifiers.is_declared(node.name):
self.identifiers.undeclared.add(node.name)
def visit_If(self, node):
self.visit(node.test)
real_identifiers = self.identifiers
- old_names = real_identifiers.declared | \
- real_identifiers.declared_locally | \
+ old_names = real_identifiers.declared_locally | \
real_identifiers.declared_parameter
def inner_visit(nodes):
# the differences between the two branches are also pulled as
# undeclared variables
- real_identifiers.undeclared.update(body.symmetric_difference(else_))
+ real_identifiers.undeclared.update(body.symmetric_difference(else_) -
+ real_identifiers.declared)
# remember those that are declared.
real_identifiers.declared_locally.update(body | else_)
class CodeGenerator(NodeVisitor):
- def __init__(self, environment, name, filename, stream=None):
+ def __init__(self, environment, name, filename, stream=None,
+ defer_init=False):
if stream is None:
stream = StringIO()
self.environment = environment
self.filename = filename
self.stream = stream
self.created_block_context = False
+ self.defer_init = defer_init
# aliases for imports
self.import_aliases = {}
# -- Various compilation helpers
def fail(self, msg, lineno):
- """Fail with a `TemplateAssertionError`."""
+ """Fail with a :exc:`TemplateAssertionError`."""
raise TemplateAssertionError(msg, lineno, self.name, self.filename)
def temporary_identifier(self):
def return_buffer_contents(self, frame):
"""Return the buffer contents of the frame."""
- if self.environment.autoescape:
+ if frame.eval_ctx.volatile:
+ self.writeline('if context.eval_ctx.autoescape:')
+ self.indent()
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+ self.writeline('return concat(%s)' % frame.buffer)
+ self.outdent()
+ elif frame.eval_ctx.autoescape:
self.writeline('return Markup(concat(%s))' % frame.buffer)
else:
self.writeline('return concat(%s)' % frame.buffer)
children = node.iter_child_nodes()
children = list(children)
func_frame = frame.inner()
- func_frame.inspect(children, hard_scope=True)
+ func_frame.inspect(children)
# variables that are undeclared (accessed before declaration) and
# declared locally *and* part of an outside scope raise a template
def visit_Template(self, node, frame=None):
assert frame is None, 'no root frame allowed'
+ eval_ctx = EvalContext(self.environment, self.name)
+
from jinja2.runtime import __all__ as exported
self.writeline('from __future__ import division')
self.writeline('from jinja2.runtime import ' + ', '.join(exported))
if not unoptimize_before_dead_code:
self.writeline('dummy = lambda *x: None')
+ # if we want a deferred initialization we cannot move the
+ # environment into a local name
+ envenv = not self.defer_init and ', environment=environment' or ''
+
# do we have an extends tag at all? If not, we can save some
# overhead by just not processing any inheritance code.
have_extends = node.find(nodes.Extends) is not None
self.writeline('name = %r' % self.name)
# generate the root render function.
- self.writeline('def root(context, environment=environment):', extra=1)
+ self.writeline('def root(context%s):' % envenv, extra=1)
# process the root
- frame = Frame()
+ frame = Frame(eval_ctx)
frame.inspect(node.body)
frame.toplevel = frame.rootlevel = True
frame.require_output_check = have_extends and not self.has_known_extends
# at this point we now have the blocks collected and can visit them too.
for name, block in self.blocks.iteritems():
- block_frame = Frame()
+ block_frame = Frame(eval_ctx)
block_frame.inspect(block.body)
block_frame.block = name
- self.writeline('def block_%s(context, environment=environment):'
- % name, block, 1)
+ self.writeline('def block_%s(context%s):' % (name, envenv),
+ block, 1)
self.indent()
undeclared = find_undeclared(block.body, ('self', 'super'))
if 'self' in undeclared:
node.iter_child_nodes(only=('else_', 'test')), ('loop',)):
self.writeline("l_loop = environment.undefined(%r, name='loop')" %
("'loop' is undefined. the filter section of a loop as well "
- "as the else block doesn't have access to the special 'loop'"
+ "as the else block don't have access to the special 'loop'"
" variable of the current loop. Because there is no parent "
"loop it's undefined. Happened in loop on %s" %
self.position(node)))
else:
finalize = unicode
- self.newline(node)
-
# if we are inside a frame that requires output checking, we do so
outdent_later = False
if frame.require_output_check:
body = []
for child in node.nodes:
try:
- const = child.as_const()
+ const = child.as_const(frame.eval_ctx)
except nodes.Impossible:
body.append(child)
continue
+ # the frame can't be volatile here, becaus otherwise the
+ # as_const() function would raise an Impossible exception
+ # at that point.
try:
- if self.environment.autoescape:
+ if frame.eval_ctx.autoescape:
if hasattr(const, '__html__'):
const = const.__html__()
else:
const = escape(const)
const = finalize(const)
- except:
+ except Exception:
# if something goes wrong here we evaluate the node
# at runtime for easier debugging
body.append(child)
else:
self.newline(item)
close = 1
- if self.environment.autoescape:
+ if frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' escape or to_string)(')
+ elif frame.eval_ctx.autoescape:
self.write('escape(')
else:
self.write('to_string(')
for argument in arguments:
self.newline(argument)
close = 0
- if self.environment.autoescape:
+ if frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' escape or to_string)(')
+ close += 1
+ elif frame.eval_ctx.autoescape:
self.write('escape(')
close += 1
if self.environment.finalize is not None:
self.write(repr(val))
def visit_TemplateData(self, node, frame):
- self.write(repr(node.as_const()))
+ try:
+ self.write(repr(node.as_const(frame.eval_ctx)))
+ except nodes.Impossible:
+ self.write('(context.eval_ctx.autoescape and Markup or identity)(%r)'
+ % node.data)
def visit_Tuple(self, node, frame):
self.write('(')
self.visit(item.value, frame)
self.write('}')
- def binop(operator):
+ def binop(operator, interceptable=True):
def visitor(self, node, frame):
- self.write('(')
- self.visit(node.left, frame)
- self.write(' %s ' % operator)
- self.visit(node.right, frame)
+ if self.environment.sandboxed and \
+ operator in self.environment.intercepted_binops:
+ self.write('environment.call_binop(context, %r, ' % operator)
+ self.visit(node.left, frame)
+ self.write(', ')
+ self.visit(node.right, frame)
+ else:
+ self.write('(')
+ self.visit(node.left, frame)
+ self.write(' %s ' % operator)
+ self.visit(node.right, frame)
self.write(')')
return visitor
- def uaop(operator):
+ def uaop(operator, interceptable=True):
def visitor(self, node, frame):
- self.write('(' + operator)
- self.visit(node.node, frame)
+ if self.environment.sandboxed and \
+ operator in self.environment.intercepted_unops:
+ self.write('environment.call_unop(context, %r, ' % operator)
+ self.visit(node.node, frame)
+ else:
+ self.write('(' + operator)
+ self.visit(node.node, frame)
self.write(')')
return visitor
visit_FloorDiv = binop('//')
visit_Pow = binop('**')
visit_Mod = binop('%')
- visit_And = binop('and')
- visit_Or = binop('or')
+ visit_And = binop('and', interceptable=False)
+ visit_Or = binop('or', interceptable=False)
visit_Pos = uaop('+')
visit_Neg = uaop('-')
- visit_Not = uaop('not ')
+ visit_Not = uaop('not ', interceptable=False)
del binop, uaop
def visit_Concat(self, node, frame):
- self.write('%s((' % (self.environment.autoescape and
- 'markup_join' or 'unicode_join'))
+ if frame.eval_ctx.volatile:
+ func_name = '(context.eval_ctx.volatile and' \
+ ' markup_join or unicode_join)'
+ elif frame.eval_ctx.autoescape:
+ func_name = 'markup_join'
+ else:
+ func_name = 'unicode_join'
+ self.write('%s((' % func_name)
for arg in node.nodes:
self.visit(arg, frame)
self.write(', ')
self.fail('no filter named %r' % node.name, node.lineno)
if getattr(func, 'contextfilter', False):
self.write('context, ')
+ elif getattr(func, 'evalcontextfilter', False):
+ self.write('context.eval_ctx, ')
elif getattr(func, 'environmentfilter', False):
self.write('environment, ')
# and want to write to the current buffer
if node.node is not None:
self.visit(node.node, frame)
- elif self.environment.autoescape:
+ elif frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' Markup(concat(%s)) or concat(%s))' %
+ (frame.buffer, frame.buffer))
+ elif frame.eval_ctx.autoescape:
self.write('Markup(concat(%s))' % frame.buffer)
else:
self.write('concat(%s)' % frame.buffer)
self.visit(node.expr, frame)
self.write(')')
+ def visit_MarkSafeIfAutoescape(self, node, frame):
+ self.write('(context.eval_ctx.autoescape and Markup or identity)(')
+ self.visit(node.expr, frame)
+ self.write(')')
+
def visit_EnvironmentAttribute(self, node, frame):
self.write('environment.' + node.name)
self.pull_locals(scope_frame)
self.blockvisit(node.body, scope_frame)
self.pop_scope(aliases, scope_frame)
+
+ def visit_EvalContextModifier(self, node, frame):
+ for keyword in node.options:
+ self.writeline('context.eval_ctx.%s = ' % keyword.key)
+ self.visit(keyword.value, frame)
+ try:
+ val = keyword.value.as_const(frame.eval_ctx)
+ except nodes.Impossible:
+ frame.eval_ctx.volatile = True
+ else:
+ setattr(frame.eval_ctx, keyword.key, val)
+
+ def visit_ScopedEvalContextModifier(self, node, frame):
+ old_ctx_name = self.temporary_identifier()
+ safed_ctx = frame.eval_ctx.save()
+ self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
+ self.visit_EvalContextModifier(node, frame)
+ for child in node.body:
+ self.visit(child, frame)
+ frame.eval_ctx.revert(safed_ctx)
+ self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
viverra volutpat vulputate'''
-
-
-#: a dict of all html entities + apos
-HTML_ENTITIES = {
- 'AElig': 198,
- 'Aacute': 193,
- 'Acirc': 194,
- 'Agrave': 192,
- 'Alpha': 913,
- 'Aring': 197,
- 'Atilde': 195,
- 'Auml': 196,
- 'Beta': 914,
- 'Ccedil': 199,
- 'Chi': 935,
- 'Dagger': 8225,
- 'Delta': 916,
- 'ETH': 208,
- 'Eacute': 201,
- 'Ecirc': 202,
- 'Egrave': 200,
- 'Epsilon': 917,
- 'Eta': 919,
- 'Euml': 203,
- 'Gamma': 915,
- 'Iacute': 205,
- 'Icirc': 206,
- 'Igrave': 204,
- 'Iota': 921,
- 'Iuml': 207,
- 'Kappa': 922,
- 'Lambda': 923,
- 'Mu': 924,
- 'Ntilde': 209,
- 'Nu': 925,
- 'OElig': 338,
- 'Oacute': 211,
- 'Ocirc': 212,
- 'Ograve': 210,
- 'Omega': 937,
- 'Omicron': 927,
- 'Oslash': 216,
- 'Otilde': 213,
- 'Ouml': 214,
- 'Phi': 934,
- 'Pi': 928,
- 'Prime': 8243,
- 'Psi': 936,
- 'Rho': 929,
- 'Scaron': 352,
- 'Sigma': 931,
- 'THORN': 222,
- 'Tau': 932,
- 'Theta': 920,
- 'Uacute': 218,
- 'Ucirc': 219,
- 'Ugrave': 217,
- 'Upsilon': 933,
- 'Uuml': 220,
- 'Xi': 926,
- 'Yacute': 221,
- 'Yuml': 376,
- 'Zeta': 918,
- 'aacute': 225,
- 'acirc': 226,
- 'acute': 180,
- 'aelig': 230,
- 'agrave': 224,
- 'alefsym': 8501,
- 'alpha': 945,
- 'amp': 38,
- 'and': 8743,
- 'ang': 8736,
- 'apos': 39,
- 'aring': 229,
- 'asymp': 8776,
- 'atilde': 227,
- 'auml': 228,
- 'bdquo': 8222,
- 'beta': 946,
- 'brvbar': 166,
- 'bull': 8226,
- 'cap': 8745,
- 'ccedil': 231,
- 'cedil': 184,
- 'cent': 162,
- 'chi': 967,
- 'circ': 710,
- 'clubs': 9827,
- 'cong': 8773,
- 'copy': 169,
- 'crarr': 8629,
- 'cup': 8746,
- 'curren': 164,
- 'dArr': 8659,
- 'dagger': 8224,
- 'darr': 8595,
- 'deg': 176,
- 'delta': 948,
- 'diams': 9830,
- 'divide': 247,
- 'eacute': 233,
- 'ecirc': 234,
- 'egrave': 232,
- 'empty': 8709,
- 'emsp': 8195,
- 'ensp': 8194,
- 'epsilon': 949,
- 'equiv': 8801,
- 'eta': 951,
- 'eth': 240,
- 'euml': 235,
- 'euro': 8364,
- 'exist': 8707,
- 'fnof': 402,
- 'forall': 8704,
- 'frac12': 189,
- 'frac14': 188,
- 'frac34': 190,
- 'frasl': 8260,
- 'gamma': 947,
- 'ge': 8805,
- 'gt': 62,
- 'hArr': 8660,
- 'harr': 8596,
- 'hearts': 9829,
- 'hellip': 8230,
- 'iacute': 237,
- 'icirc': 238,
- 'iexcl': 161,
- 'igrave': 236,
- 'image': 8465,
- 'infin': 8734,
- 'int': 8747,
- 'iota': 953,
- 'iquest': 191,
- 'isin': 8712,
- 'iuml': 239,
- 'kappa': 954,
- 'lArr': 8656,
- 'lambda': 955,
- 'lang': 9001,
- 'laquo': 171,
- 'larr': 8592,
- 'lceil': 8968,
- 'ldquo': 8220,
- 'le': 8804,
- 'lfloor': 8970,
- 'lowast': 8727,
- 'loz': 9674,
- 'lrm': 8206,
- 'lsaquo': 8249,
- 'lsquo': 8216,
- 'lt': 60,
- 'macr': 175,
- 'mdash': 8212,
- 'micro': 181,
- 'middot': 183,
- 'minus': 8722,
- 'mu': 956,
- 'nabla': 8711,
- 'nbsp': 160,
- 'ndash': 8211,
- 'ne': 8800,
- 'ni': 8715,
- 'not': 172,
- 'notin': 8713,
- 'nsub': 8836,
- 'ntilde': 241,
- 'nu': 957,
- 'oacute': 243,
- 'ocirc': 244,
- 'oelig': 339,
- 'ograve': 242,
- 'oline': 8254,
- 'omega': 969,
- 'omicron': 959,
- 'oplus': 8853,
- 'or': 8744,
- 'ordf': 170,
- 'ordm': 186,
- 'oslash': 248,
- 'otilde': 245,
- 'otimes': 8855,
- 'ouml': 246,
- 'para': 182,
- 'part': 8706,
- 'permil': 8240,
- 'perp': 8869,
- 'phi': 966,
- 'pi': 960,
- 'piv': 982,
- 'plusmn': 177,
- 'pound': 163,
- 'prime': 8242,
- 'prod': 8719,
- 'prop': 8733,
- 'psi': 968,
- 'quot': 34,
- 'rArr': 8658,
- 'radic': 8730,
- 'rang': 9002,
- 'raquo': 187,
- 'rarr': 8594,
- 'rceil': 8969,
- 'rdquo': 8221,
- 'real': 8476,
- 'reg': 174,
- 'rfloor': 8971,
- 'rho': 961,
- 'rlm': 8207,
- 'rsaquo': 8250,
- 'rsquo': 8217,
- 'sbquo': 8218,
- 'scaron': 353,
- 'sdot': 8901,
- 'sect': 167,
- 'shy': 173,
- 'sigma': 963,
- 'sigmaf': 962,
- 'sim': 8764,
- 'spades': 9824,
- 'sub': 8834,
- 'sube': 8838,
- 'sum': 8721,
- 'sup': 8835,
- 'sup1': 185,
- 'sup2': 178,
- 'sup3': 179,
- 'supe': 8839,
- 'szlig': 223,
- 'tau': 964,
- 'there4': 8756,
- 'theta': 952,
- 'thetasym': 977,
- 'thinsp': 8201,
- 'thorn': 254,
- 'tilde': 732,
- 'times': 215,
- 'trade': 8482,
- 'uArr': 8657,
- 'uacute': 250,
- 'uarr': 8593,
- 'ucirc': 251,
- 'ugrave': 249,
- 'uml': 168,
- 'upsih': 978,
- 'upsilon': 965,
- 'uuml': 252,
- 'weierp': 8472,
- 'xi': 958,
- 'yacute': 253,
- 'yen': 165,
- 'yuml': 255,
- 'zeta': 950,
- 'zwj': 8205,
- 'zwnj': 8204
-}
"""
import sys
import traceback
+from types import TracebackType
from jinja2.utils import CodeType, missing, internal_code
from jinja2.exceptions import TemplateSyntaxError
+# on pypy we can take advantage of transparent proxies
+try:
+ from __pypy__ import tproxy
+except ImportError:
+ tproxy = None
+
# how does the raise helper look like?
try:
def __init__(self, tb):
self.tb = tb
+ self._tb_next = None
- def _set_tb_next(self, next):
- if tb_set_next is not None:
- tb_set_next(self.tb, next and next.tb or None)
- self._tb_next = next
-
- def _get_tb_next(self):
+ @property
+ def tb_next(self):
return self._tb_next
- tb_next = property(_get_tb_next, _set_tb_next)
- del _get_tb_next, _set_tb_next
+ def set_next(self, next):
+ if tb_set_next is not None:
+ try:
+ tb_set_next(self.tb, next and next.tb or None)
+ except Exception:
+ # this function can fail due to all the hackery it does
+ # on various python implementations. We just catch errors
+ # down and ignore them if necessary.
+ pass
+ self._tb_next = next
@property
def is_jinja_frame(self):
return getattr(self.tb, name)
+def make_frame_proxy(frame):
+ proxy = TracebackFrameProxy(frame)
+ if tproxy is None:
+ return proxy
+ def operation_handler(operation, *args, **kwargs):
+ if operation in ('__getattribute__', '__getattr__'):
+ return getattr(proxy, args[0])
+ elif operation == '__setattr__':
+ proxy.__setattr__(*args, **kwargs)
+ else:
+ return getattr(proxy, operation)(*args, **kwargs)
+ return tproxy(TracebackType, operation_handler)
+
+
class ProcessedTraceback(object):
"""Holds a Jinja preprocessed traceback for priting or reraising."""
self.exc_value = exc_value
self.frames = frames
- def chain_frames(self):
- """Chains the frames. Requires ctypes or the speedups extension."""
+ # newly concatenate the frames (which are proxies)
prev_tb = None
for tb in self.frames:
if prev_tb is not None:
- prev_tb.tb_next = tb
+ prev_tb.set_next(tb)
prev_tb = tb
- prev_tb.tb_next = None
+ prev_tb.set_next(None)
def render_as_text(self, limit=None):
"""Return a string with the traceback."""
@property
def standard_exc_info(self):
"""Standard python exc_info for re-raising"""
- return self.exc_type, self.exc_value, self.frames[0].tb
+ tb = self.frames[0]
+ # the frame will be an actual traceback (or transparent proxy) if
+ # we are on pypy or a python implementation with support for tproxy
+ if type(tb) is not TracebackType:
+ tb = tb.tb
+ return self.exc_type, self.exc_value, tb
def make_traceback(exc_info, source_hint=None):
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
lineno)[2]
- frames.append(TracebackFrameProxy(tb))
+ frames.append(make_frame_proxy(tb))
tb = next
# if we don't have any exceptions in the frames left, we have to
if not frames:
raise exc_info[0], exc_info[1], exc_info[2]
- traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
- if tb_set_next is not None:
- traceback.chain_frames()
- return traceback
+ return ProcessedTraceback(exc_info[0], exc_info[1], frames)
def fake_exc_info(exc_info, filename, lineno):
def _init_ugly_crap():
"""This function implements a few ugly things so that we can patch the
traceback objects. The function returned allows resetting `tb_next` on
- any python traceback object.
+ any python traceback object. Do not attempt to use this on non cpython
+ interpreters
"""
import ctypes
from types import TracebackType
]
# python with trace
- if object.__basicsize__ != ctypes.sizeof(_PyObject):
+ if hasattr(sys, 'getobjects'):
class _PyObject(ctypes.Structure):
pass
_PyObject._fields_ = [
return tb_set_next
-# try to get a tb_set_next implementation
-try:
- from jinja2._speedups import tb_set_next
-except ImportError:
+# try to get a tb_set_next implementation if we don't have transparent
+# proxies.
+tb_set_next = None
+if tproxy is None:
try:
- tb_set_next = _init_ugly_crap()
- except:
- tb_set_next = None
-del _init_ugly_crap
+ from jinja2._debugsupport import tb_set_next
+ except ImportError:
+ try:
+ tb_set_next = _init_ugly_crap()
+ except:
+ pass
+ del _init_ugly_crap
NEWLINE_SEQUENCE = '\n'
-try:
- range_func = xrange
-except NameError:
- range_func = range
-
-
# default filters, tests and namespace
from jinja2.filters import FILTERS as DEFAULT_FILTERS
from jinja2.tests import TESTS as DEFAULT_TESTS
DEFAULT_NAMESPACE = {
- 'range': range_func,
+ 'range': xrange,
'dict': lambda **kw: kw,
'lipsum': generate_lorem_ipsum,
'cycler': Cycler,
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
+import os
import sys
from jinja2 import nodes
from jinja2.defaults import *
`None` implicitly into an empty string here.
`autoescape`
- If set to true the XML/HTML autoescaping feature is enabled.
- For more details about auto escaping see
- :class:`~jinja2.utils.Markup`.
+ If set to true the XML/HTML autoescaping feature is enabled by
+ default. For more details about auto escaping see
+ :class:`~jinja2.utils.Markup`. As of Jinja 2.4 this can also
+ be a callable that is passed the template name and has to
+ return `True` or `False` depending on autoescape should be
+ enabled by default.
+
+ .. versionchanged:: 2.4
+ `autoescape` can now be a function
`loader`
The template loader for this environment.
#: if this environment is sandboxed. Modifying this variable won't make
#: the environment sandboxed though. For a real sandboxed environment
- #: have a look at jinja2.sandbox
+ #: have a look at jinja2.sandbox. This flag alone controls the code
+ #: generation by the compiler.
sandboxed = False
#: True if the environment is just an overlay
_environment_sanity_check(self)
+ def add_extension(self, extension):
+ """Adds an extension after the environment was created.
+
+ .. versionadded:: 2.5
+ """
+ self.extensions.update(load_extensions(self, [extension]))
+
def extend(self, **attributes):
"""Add the items to the instance of the environment if they do not exist
yet. This is used by :ref:`extensions <writing-extensions>` to register
for key, value in self.extensions.iteritems():
rv.extensions[key] = value.bind(rv)
if extensions is not missing:
- rv.extensions.update(load_extensions(extensions))
+ rv.extensions.update(load_extensions(rv, extensions))
return _environment_sanity_check(rv)
lexer = property(get_lexer, doc="The lexer for this environment.")
+ def iter_extensions(self):
+ """Iterates over the extensions by priority."""
+ return iter(sorted(self.extensions.values(),
+ key=lambda x: x.priority))
+
def getitem(self, obj, argument):
"""Get an item or attribute of an object but prefer the item."""
try:
if isinstance(argument, basestring):
try:
attr = str(argument)
- except:
+ except Exception:
pass
else:
try:
because there you usually only want the actual source tokenized.
"""
return reduce(lambda s, e: e.preprocess(s, name, filename),
- self.extensions.itervalues(), unicode(source))
+ self.iter_extensions(), unicode(source))
def _tokenize(self, source, name, filename=None, state=None):
"""Called by the parser to do the preprocessing and filtering
"""
source = self.preprocess(source, name, filename)
stream = self.lexer.tokenize(source, name, filename, state)
- for ext in self.extensions.itervalues():
+ for ext in self.iter_extensions():
stream = ext.filter_stream(stream)
if not isinstance(stream, TokenStream):
stream = TokenStream(stream, name, filename)
return stream
+ def _generate(self, source, name, filename, defer_init=False):
+ """Internal hook that can be overriden to hook a different generate
+ method in.
+
+ .. versionadded:: 2.5
+ """
+ return generate(source, self, name, filename, defer_init=defer_init)
+
+ def _compile(self, source, filename):
+ """Internal hook that can be overriden to hook a different compile
+ method in.
+
+ .. versionadded:: 2.5
+ """
+ return compile(source, filename, 'exec')
+
@internalcode
- def compile(self, source, name=None, filename=None, raw=False):
+ def compile(self, source, name=None, filename=None, raw=False,
+ defer_init=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.
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.
+
+ `defer_init` is use internally to aid the module code generator. This
+ causes the generated code to be able to import without the global
+ environment variable to be set.
+
+ .. versionadded:: 2.4
+ `defer_init` parameter added.
"""
source_hint = None
try:
source = self._parse(source, name, filename)
if self.optimized:
source = optimize(source, self)
- source = generate(source, self, name, filename)
+ source = self._generate(source, name, filename,
+ defer_init=defer_init)
if raw:
return source
if filename is None:
filename = '<template>'
else:
filename = _encode_filename(filename)
- return compile(source, filename, 'exec')
+ return self._compile(source, filename)
except TemplateSyntaxError:
exc_info = sys.exc_info()
self.handle_exception(exc_info, source_hint=source)
raise TemplateSyntaxError('chunk after expression',
parser.stream.current.lineno,
None, None)
+ expr.set_environment(self)
except TemplateSyntaxError:
exc_info = sys.exc_info()
if exc_info is not None:
template = self.from_string(nodes.Template(body, lineno=1))
return TemplateExpression(template, undefined_to_none)
+ def compile_templates(self, target, extensions=None, filter_func=None,
+ zip='deflated', log_function=None,
+ ignore_errors=True, py_compile=False):
+ """Finds all the templates the loader can find, compiles them
+ and stores them in `target`. If `zip` is `None`, instead of in a
+ zipfile, the templates will be will be stored in a directory.
+ By default a deflate zip algorithm is used, to switch to
+ the stored algorithm, `zip` can be set to ``'stored'``.
+
+ `extensions` and `filter_func` are passed to :meth:`list_templates`.
+ Each template returned will be compiled to the target folder or
+ zipfile.
+
+ By default template compilation errors are ignored. In case a
+ log function is provided, errors are logged. If you want template
+ syntax errors to abort the compilation you can set `ignore_errors`
+ to `False` and you will get an exception on syntax errors.
+
+ If `py_compile` is set to `True` .pyc files will be written to the
+ target instead of standard .py files.
+
+ .. versionadded:: 2.4
+ """
+ from jinja2.loaders import ModuleLoader
+
+ if log_function is None:
+ log_function = lambda x: None
+
+ if py_compile:
+ import imp, marshal
+ py_header = imp.get_magic() + \
+ u'\xff\xff\xff\xff'.encode('iso-8859-15')
+
+ def write_file(filename, data, mode):
+ if zip:
+ info = ZipInfo(filename)
+ info.external_attr = 0755 << 16L
+ zip_file.writestr(info, data)
+ else:
+ f = open(os.path.join(target, filename), mode)
+ try:
+ f.write(data)
+ finally:
+ f.close()
+
+ if zip is not None:
+ from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
+ zip_file = ZipFile(target, 'w', dict(deflated=ZIP_DEFLATED,
+ stored=ZIP_STORED)[zip])
+ log_function('Compiling into Zip archive "%s"' % target)
+ else:
+ if not os.path.isdir(target):
+ os.makedirs(target)
+ log_function('Compiling into folder "%s"' % target)
+
+ try:
+ for name in self.list_templates(extensions, filter_func):
+ source, filename, _ = self.loader.get_source(self, name)
+ try:
+ code = self.compile(source, name, filename, True, True)
+ except TemplateSyntaxError, e:
+ if not ignore_errors:
+ raise
+ log_function('Could not compile "%s": %s' % (name, e))
+ continue
+
+ filename = ModuleLoader.get_module_filename(name)
+
+ if py_compile:
+ c = self._compile(code, _encode_filename(filename))
+ write_file(filename + 'c', py_header +
+ marshal.dumps(c), 'wb')
+ log_function('Byte-compiled "%s" as %s' %
+ (name, filename + 'c'))
+ else:
+ write_file(filename, code, 'w')
+ log_function('Compiled "%s" as %s' % (name, filename))
+ finally:
+ if zip:
+ zip_file.close()
+
+ log_function('Finished compiling templates')
+
+ def list_templates(self, extensions=None, filter_func=None):
+ """Returns a list of templates for this environment. This requires
+ that the loader supports the loader's
+ :meth:`~BaseLoader.list_templates` method.
+
+ If there are other files in the template folder besides the
+ actual templates, the returned list can be filtered. There are two
+ ways: either `extensions` is set to a list of file extensions for
+ templates, or a `filter_func` can be provided which is a callable that
+ is passed a template name and should return `True` if it should end up
+ in the result list.
+
+ If the loader does not support that, a :exc:`TypeError` is raised.
+
+ .. versionadded:: 2.4
+ """
+ x = self.loader.list_templates()
+ if extensions is not None:
+ if filter_func is not None:
+ raise TypeError('either extensions or filter_func '
+ 'can be passed, but not both')
+ filter_func = lambda x: '.' in x and \
+ x.rsplit('.', 1)[1] in extensions
+ if filter_func is not None:
+ x = filter(filter_func, x)
+ return x
+
def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
"""Exception handling helper. This is used internally to either raise
rewritten exceptions or return a rendered traceback for the template.
If the template does not exist a :exc:`TemplateNotFound` exception is
raised.
+
+ .. versionchanged:: 2.4
+ If `name` is a :class:`Template` object it is returned from the
+ function unchanged.
"""
+ if isinstance(name, Template):
+ return name
if parent is not None:
name = self.join_path(name, parent)
return self._load_template(name, self.make_globals(globals))
raise a :exc:`TemplatesNotFound` exception.
.. versionadded:: 2.3
+
+ .. versionchanged:: 2.4
+ If `names` contains a :class:`Template` object it is returned
+ from the function unchanged.
"""
if not names:
raise TemplatesNotFound(message=u'Tried to select from an empty list '
u'of templates.')
globals = self.make_globals(globals)
for name in names:
+ if isinstance(name, Template):
+ return name
if parent is not None:
name = self.join_path(name, parent)
try:
@internalcode
def get_or_select_template(self, template_name_or_list,
parent=None, globals=None):
- """
- Does a typecheck and dispatches to :meth:`select_template` if an
- iterable of template names is given, otherwise to :meth:`get_template`.
+ """Does a typecheck and dispatches to :meth:`select_template`
+ if an iterable of template names is given, otherwise to
+ :meth:`get_template`.
.. versionadded:: 2.3
"""
if isinstance(template_name_or_list, basestring):
return self.get_template(template_name_or_list, parent, globals)
+ elif isinstance(template_name_or_list, Template):
+ return template_name_or_list
return self.select_template(template_name_or_list, parent, globals)
def from_string(self, source, globals=None, template_class=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
+ 'environment': environment,
+ '__file__': code.co_filename
}
exec code in namespace
+ rv = cls._from_namespace(environment, namespace, globals)
+ rv._uptodate = uptodate
+ return rv
+
+ @classmethod
+ def from_module_dict(cls, environment, module_dict, globals):
+ """Creates a template object from a module. This is used by the
+ module loader to create a template object.
+
+ .. versionadded:: 2.4
+ """
+ return cls._from_namespace(environment, module_dict, globals)
+
+ @classmethod
+ def _from_namespace(cls, environment, namespace, globals):
+ t = object.__new__(cls)
t.environment = environment
t.globals = globals
t.name = namespace['name']
- t.filename = code.co_filename
+ t.filename = namespace['__file__']
t.blocks = namespace['blocks']
# render function and module
# debug and loader helpers
t._debug_info = namespace['debug_info']
- t._uptodate = uptodate
+ t._uptodate = None
+
+ # store the reference
+ namespace['environment'] = environment
+ namespace['__jinja_template__'] = t
return t
vars = dict(*args, **kwargs)
try:
return concat(self.root_render_func(self.new_context(vars)))
- except:
+ except Exception:
exc_info = sys.exc_info()
return self.environment.handle_exception(exc_info, True)
try:
for event in self.root_render_func(self.new_context(vars)):
yield event
- except:
+ except Exception:
exc_info = sys.exc_info()
else:
return
from collections import deque
from jinja2 import nodes
from jinja2.defaults import *
-from jinja2.environment import get_spontaneous_environment
+from jinja2.environment import Environment
from jinja2.runtime import Undefined, concat
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
from jinja2.utils import contextfunction, import_string, Markup, next
#: if this extension parses this is the list of tags it's listening to.
tags = set()
+ #: the priority of that extension. This is especially useful for
+ #: extensions that preprocess values. A lower value means higher
+ #: priority.
+ #:
+ #: .. versionadded:: 2.4
+ priority = 100
+
def __init__(self, environment):
self.environment = environment
def attr(self, name, lineno=None):
"""Return an attribute node for the current extension. This is useful
- to pass constants on extensions to generated template code::
+ to pass constants on extensions to generated template code.
+
+ ::
self.attr('_my_attribute', lineno=lineno)
"""
@contextfunction
-def _gettext_alias(context, string):
- return context.resolve('gettext')(string)
+def _gettext_alias(__context, *args, **kwargs):
+ return __context.call(__context.resolve('gettext'), *args, **kwargs)
+
+
+def _make_new_gettext(func):
+ @contextfunction
+ def gettext(__context, __string, **variables):
+ rv = __context.call(func, __string)
+ if __context.eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv % variables
+ return gettext
+
+
+def _make_new_ngettext(func):
+ @contextfunction
+ def ngettext(__context, __singular, __plural, __num, **variables):
+ variables.setdefault('num', __num)
+ rv = __context.call(func, __singular, __plural, __num)
+ if __context.eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv % variables
+ return ngettext
class InternationalizationExtension(Extension):
environment.extend(
install_gettext_translations=self._install,
install_null_translations=self._install_null,
+ install_gettext_callables=self._install_callables,
uninstall_gettext_translations=self._uninstall,
- extract_translations=self._extract
+ extract_translations=self._extract,
+ newstyle_gettext=False
)
- def _install(self, translations):
+ def _install(self, translations, newstyle=None):
gettext = getattr(translations, 'ugettext', None)
if gettext is None:
gettext = translations.gettext
ngettext = getattr(translations, 'ungettext', None)
if ngettext is None:
ngettext = translations.ngettext
- self.environment.globals.update(gettext=gettext, ngettext=ngettext)
+ self._install_callables(gettext, ngettext, newstyle)
+
+ def _install_null(self, newstyle=None):
+ self._install_callables(
+ lambda x: x,
+ lambda s, p, n: (n != 1 and (p,) or (s,))[0],
+ newstyle
+ )
- def _install_null(self):
+ def _install_callables(self, gettext, ngettext, newstyle=None):
+ if newstyle is not None:
+ self.environment.newstyle_gettext = newstyle
+ if self.environment.newstyle_gettext:
+ gettext = _make_new_gettext(gettext)
+ ngettext = _make_new_ngettext(ngettext)
self.environment.globals.update(
- gettext=lambda x: x,
- ngettext=lambda s, p, n: (n != 1 and (p,) or (s,))[0]
+ gettext=gettext,
+ ngettext=ngettext
)
def _uninstall(self, translations):
def parse(self, parser):
"""Parse a translatable tag."""
lineno = next(parser.stream).lineno
+ num_called_num = False
# find all the variables referenced. Additionally a variable can be
# defined in the body of the trans block too, but this is checked at
variables[name.value] = var = parser.parse_expression()
else:
variables[name.value] = var = nodes.Name(name.value, 'load')
+
if plural_expr is None:
plural_expr = var
+ num_called_num = name.value == 'num'
parser.stream.expect('block_end')
referenced.update(singular_names)
if plural_expr is None:
plural_expr = nodes.Name(singular_names[0], 'load')
+ num_called_num = singular_names[0] == 'num'
# if we have a pluralize block, we parse that too
if parser.stream.current.test('name:pluralize'):
name.value, name.lineno,
exc=TemplateAssertionError)
plural_expr = variables[name.value]
+ num_called_num = name.value == 'num'
parser.stream.expect('block_end')
plural_names, plural = self._parse_block(parser, False)
next(parser.stream)
if var not in variables:
variables[var] = nodes.Name(var, 'load')
- # no variables referenced? no need to escape
- if not referenced:
- singular = singular.replace('%%', '%')
- if plural:
- plural = plural.replace('%%', '%')
-
if not have_plural:
plural_expr = None
elif plural_expr is None:
parser.fail('pluralize without variables', lineno)
- if variables:
- variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
- for x, y in variables.items()])
- else:
- variables = None
-
- node = self._make_node(singular, plural, variables, plural_expr)
+ node = self._make_node(singular, plural, variables, plural_expr,
+ bool(referenced),
+ num_called_num and have_plural)
node.set_lineno(lineno)
return node
return referenced, concat(buf)
- def _make_node(self, singular, plural, variables, plural_expr):
+ def _make_node(self, singular, plural, variables, plural_expr,
+ vars_referenced, num_called_num):
"""Generates a useful node from the data provided."""
+ # no variables referenced? no need to escape for old style
+ # gettext invocations only if there are vars.
+ if not vars_referenced and not self.environment.newstyle_gettext:
+ singular = singular.replace('%%', '%')
+ if plural:
+ plural = plural.replace('%%', '%')
+
# singular only:
if plural_expr is None:
gettext = nodes.Name('gettext', 'load')
plural_expr
], [], None, None)
- # 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)
+ # in case newstyle gettext is used, the method is powerful
+ # enough to handle the variable expansion and autoescape
+ # handling itself
+ if self.environment.newstyle_gettext:
+ for key, value in variables.iteritems():
+ # the function adds that later anyways in case num was
+ # called num, so just skip it.
+ if num_called_num and key == 'num':
+ continue
+ node.kwargs.append(nodes.Keyword(key, value))
+
+ # otherwise do that here
+ else:
+ # mark the return value as safe if we are in an
+ # environment with autoescaping turned on
+ node = nodes.MarkSafeIfAutoescape(node)
+ if variables:
+ node = nodes.Mod(node, nodes.Dict([
+ nodes.Pair(nodes.Const(key), value)
+ for key, value in variables.items()
+ ]))
return nodes.Output([node])
return node
+class AutoEscapeExtension(Extension):
+ """Changes auto escape rules for a scope."""
+ tags = set(['autoescape'])
+
+ def parse(self, parser):
+ node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
+ node.options = [
+ nodes.Keyword('autoescape', parser.parse_expression())
+ ]
+ node.body = parser.parse_statements(('name:endautoescape',),
+ drop_needle=True)
+ return nodes.Scope([node])
+
+
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
babel_style=True):
"""Extract localizable strings from the given template node. Per
gettext call in one line of code and the matching comment in the
same line or the line before.
+ .. versionchanged:: 2.5.1
+ The `newstyle_gettext` flag can be set to `True` to enable newstyle
+ gettext calls.
+
:param fileobj: the file-like object the messages should be extracted from
:param keywords: a list of keywords (i.e. function names) that should be
recognized as translation functions
if InternationalizationExtension not in extensions:
extensions.add(InternationalizationExtension)
- environment = get_spontaneous_environment(
+ def getbool(options, key, default=False):
+ options.get(key, str(default)).lower() in ('1', 'on', 'yes', 'true')
+
+ environment = Environment(
options.get('block_start_string', BLOCK_START_STRING),
options.get('block_end_string', BLOCK_END_STRING),
options.get('variable_start_string', VARIABLE_START_STRING),
options.get('comment_end_string', COMMENT_END_STRING),
options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
- str(options.get('trim_blocks', TRIM_BLOCKS)).lower() in \
- ('1', 'on', 'yes', 'true'),
+ getbool(options, 'trim_blocks', TRIM_BLOCKS),
NEWLINE_SEQUENCE, frozenset(extensions),
- # fill with defaults so that environments are shared
- # with other spontaneus environments. The rest of the
- # arguments are optimizer, undefined, finalize, autoescape,
- # loader, cache size, auto reloading setting and the
- # bytecode cache
- True, Undefined, None, False, None, 0, False, None
+ cache_size=0,
+ auto_reload=False
)
+ if getbool(options, 'newstyle_gettext'):
+ environment.newstyle_gettext = True
+
source = fileobj.read().decode(options.get('encoding', 'utf-8'))
try:
node = environment.parse(source)
do = ExprStmtExtension
loopcontrols = LoopControlExtension
with_ = WithExtension
+autoescape = AutoEscapeExtension
"""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')
f.contextfilter = True
return f
+def evalcontextfilter(f):
+ """Decorator for marking eval-context dependent filters. An eval
+ context object is passed as first argument. For more information
+ about the eval context, see :ref:`eval-context`.
+
+ .. versionadded:: 2.4
+ """
+ f.evalcontextfilter = True
+ return f
+
+
def environmentfilter(f):
"""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')
f.environmentfilter = True
return f
+def make_attrgetter(environment, attribute):
+ """Returns a callable that looks up the given attribute from a
+ passed object with the rules of the environment. Dots are allowed
+ to access attributes of attributes.
+ """
+ if not isinstance(attribute, basestring) or '.' not in attribute:
+ return lambda x: environment.getitem(x, attribute)
+ attribute = attribute.split('.')
+ def attrgetter(item):
+ for part in attribute:
+ item = environment.getitem(item, part)
+ return item
+ return attrgetter
+
+
def do_forceescape(value):
"""Enforce HTML escaping. This will probably double escape variables."""
if hasattr(value, '__html__'):
return escape(unicode(value))
-@environmentfilter
-def do_replace(environment, s, old, new, count=None):
+@evalcontextfilter
+def do_replace(eval_ctx, 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.
"""
if count is None:
count = -1
- if not environment.autoescape:
+ if not eval_ctx.autoescape:
return unicode(s).replace(unicode(old), unicode(new), count)
if hasattr(old, '__html__') or hasattr(new, '__html__') and \
not hasattr(s, '__html__'):
return soft_unicode(s).lower()
-@environmentfilter
-def do_xmlattr(_environment, d, autospace=True):
+@evalcontextfilter
+def do_xmlattr(_eval_ctx, d, autospace=True):
"""Create an SGML/XML attribute string based on the items in a dict.
All values that are neither `none` nor `undefined` are automatically
escaped:
)
if autospace and rv:
rv = u' ' + rv
- if _environment.autoescape:
+ if _eval_ctx.autoescape:
rv = Markup(rv)
return rv
return sorted(value.items(), key=sort_func)
-def do_sort(value, case_sensitive=False):
- """Sort an iterable. If the iterable is made of strings the second
- parameter can be used to control the case sensitiveness of the
- comparison which is disabled by default.
+@environmentfilter
+def do_sort(environment, value, reverse=False, case_sensitive=False,
+ attribute=None):
+ """Sort an iterable. Per default it sorts ascending, if you pass it
+ true as first argument it will reverse the sorting.
+
+ If the iterable is made of strings the third parameter can be used to
+ control the case sensitiveness of the comparison which is disabled by
+ default.
.. sourcecode:: jinja
{% for item in iterable|sort %}
...
{% endfor %}
+
+ It is also possible to sort by an attribute (for example to sort
+ by the date of an object) by specifying the `attribute` parameter:
+
+ .. sourcecode:: jinja
+
+ {% for item in iterable|sort(attribute='date') %}
+ ...
+ {% endfor %}
+
+ .. versionchanged:: 2.6
+ The `attribute` parameter was added.
"""
if not case_sensitive:
def sort_func(item):
return item
else:
sort_func = None
- return sorted(seq, key=sort_func)
+ if attribute is not None:
+ getter = make_attrgetter(environment, attribute)
+ def sort_func(item, processor=sort_func or (lambda x: x)):
+ return processor(getter(item))
+ return sorted(value, key=sort_func, reverse=reverse)
def do_default(value, default_value=u'', boolean=False):
return value
-@environmentfilter
-def do_join(environment, value, d=u''):
+@evalcontextfilter
+def do_join(eval_ctx, value, d=u'', attribute=None):
"""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 it with the optional parameter:
{{ [1, 2, 3]|join }}
-> 123
+
+ It is also possible to join certain attributes of an object:
+
+ .. sourcecode:: jinja
+
+ {{ users|join(', ', attribute='username') }}
+
+ .. versionadded:: 2.6
+ The `attribute` parameter was added.
"""
+ if attribute is not None:
+ value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
+
# no automatic escaping? joining is a lot eaiser then
- if not environment.autoescape:
+ if not eval_ctx.autoescape:
return unicode(d).join(imap(unicode, value))
# if the delimiter doesn't have an html representation we check
def do_filesizeformat(value, binary=False):
- """Format the value like a 'human-readable' file size (i.e. 13 KB,
- 4.1 MB, 102 bytes, etc). Per default decimal prefixes are used (mega,
- giga, etc.), if the second parameter is set to `True` the binary
- prefixes are used (mebi, gibi).
+ """Format the value like a 'human-readable' file size (i.e. 13 kB,
+ 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
+ Giga, etc.), if the second parameter is set to `True` the binary
+ prefixes are used (Mebi, Gibi).
"""
bytes = float(value)
base = binary and 1024 or 1000
- middle = binary and 'i' or ''
- if bytes < base:
- return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
- elif bytes < base * base:
- return "%.1f K%sB" % (bytes / base, middle)
- elif bytes < base * base * base:
- return "%.1f M%sB" % (bytes / (base * base), middle)
- return "%.1f G%sB" % (bytes / (base * base * base), middle)
+ prefixes = [
+ (binary and "KiB" or "kB"),
+ (binary and "MiB" or "MB"),
+ (binary and "GiB" or "GB"),
+ (binary and "TiB" or "TB"),
+ (binary and "PiB" or "PB"),
+ (binary and "EiB" or "EB"),
+ (binary and "ZiB" or "ZB"),
+ (binary and "YiB" or "YB")
+ ]
+ if bytes == 1:
+ return "1 Byte"
+ elif bytes < base:
+ return "%d Bytes" % bytes
+ else:
+ for i, prefix in enumerate(prefixes):
+ unit = base * base ** (i + 1)
+ if bytes < unit:
+ return "%.1f %s" % ((bytes / unit), prefix)
+ return "%.1f %s" % ((bytes / unit), prefix)
def do_pprint(value, verbose=False):
return pformat(value, verbose=verbose)
-@environmentfilter
-def do_urlize(environment, value, trim_url_limit=None, nofollow=False):
+@evalcontextfilter
+def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
"""Converts URLs in plain text into clickable links.
If you pass the filter an additional integer it will shorten the urls
links are shortened to 40 chars and defined with rel="nofollow"
"""
rv = urlize(value, trim_url_limit, nofollow)
- if environment.autoescape:
+ if eval_ctx.autoescape:
rv = Markup(rv)
return rv
result.append(end)
return u' '.join(result)
-
-def do_wordwrap(s, width=79, break_long_words=True):
+@environmentfilter
+def do_wordwrap(environment, s, width=79, break_long_words=True):
"""
Return a copy of the string passed to the filter wrapped after
``79`` characters. You can override this default using the first
split words apart if they are longer than `width`.
"""
import textwrap
- return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False,
+ return environment.newline_sequence.join(textwrap.wrap(s, width=width, expand_tabs=False,
replace_whitespace=False,
break_long_words=break_long_words))
"""
if not method in ('common', 'ceil', 'floor'):
raise FilterArgumentError('method must be common, ceil or floor')
- if precision < 0:
- raise FilterArgumentError('precision must be a postive integer '
- 'or zero.')
if method == 'common':
return round(value, precision)
func = getattr(math, method)
- if precision:
- return func(value * 10 * precision) / (10 * precision)
- else:
- return func(value)
-
-
-def do_sort(value, reverse=False):
- """Sort a sequence. Per default it sorts ascending, if you pass it
- true as first argument it will reverse the sorting.
- """
- return sorted(value, reverse=reverse)
+ return func(value * (10 ** precision)) / (10 ** precision)
@environmentfilter
As you can see the item we're grouping by is stored in the `grouper`
attribute and the `list` contains all the objects that have this grouper
in common.
+
+ .. versionchanged:: 2.6
+ It's now possible to use dotted notation to group by the child
+ attribute of another attribute.
"""
- expr = lambda x: environment.getitem(x, attribute)
+ expr = make_attrgetter(environment, attribute)
return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
return tuple.__new__(cls, (key, list(value)))
+@environmentfilter
+def do_sum(environment, iterable, attribute=None, start=0):
+ """Returns the sum of a sequence of numbers plus the value of parameter
+ 'start' (which defaults to 0). When the sequence is empty it returns
+ start.
+
+ It is also possible to sum up only certain attributes:
+
+ .. sourcecode:: jinja
+
+ Total: {{ items|sum(attribute='price') }}
+
+ .. versionchanged:: 2.6
+ The `attribute` parameter was added to allow suming up over
+ attributes. Also the `start` parameter was moved on to the right.
+ """
+ if attribute is not None:
+ iterable = imap(make_attrgetter(environment, attribute), iterable)
+ return sum(iterable, start)
+
+
def do_list(value):
"""Convert the value into a list. If it was a string the returned list
will be a list of characters.
'striptags': do_striptags,
'slice': do_slice,
'batch': do_batch,
- 'sum': sum,
+ 'sum': do_sum,
'abs': abs,
'round': do_round,
- 'sort': do_sort,
'groupby': do_groupby,
'safe': do_mark_safe,
'xmlattr': do_xmlattr
'root': [
# directives
(c('(.*?)(?:%s)' % '|'.join(
- [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*%s)' % (
+ [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % (
e(environment.block_start_string),
e(environment.block_start_string),
+ e(environment.block_end_string),
e(environment.block_end_string)
)] + [
r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, r)
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
+import os
+import sys
+import weakref
+from types import ModuleType
from os import path
try:
from hashlib import sha1
return source, path, lambda: mtime == getmtime(path)
"""
+ #: if set to `False` it indicates that the loader cannot provide access
+ #: to the source of templates.
+ #:
+ #: .. versionadded:: 2.4
+ has_source_access = True
+
def get_source(self, environment, template):
"""Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
old state somewhere (for example in a closure). If it returns `False`
the template will be reloaded.
"""
+ if not self.has_source_access:
+ raise RuntimeError('%s cannot provide access to the source' %
+ self.__class__.__name__)
raise TemplateNotFound(template)
+ def list_templates(self):
+ """Iterates over all templates. If the loader does not support that
+ it should raise a :exc:`TypeError` which is the default behavior.
+ """
+ raise TypeError('this loader cannot iterate over all templates')
+
@internalcode
def load(self, environment, name, globals=None):
"""Loads a template. This method looks up the template in the cache
return contents, filename, uptodate
raise TemplateNotFound(template)
+ def list_templates(self):
+ found = set()
+ for searchpath in self.searchpath:
+ for dirpath, dirnames, filenames in os.walk(searchpath):
+ for filename in filenames:
+ template = os.path.join(dirpath, filename) \
+ [len(searchpath):].strip(os.path.sep) \
+ .replace(os.path.sep, '/')
+ if template[:2] == './':
+ template = template[2:]
+ if template not in found:
+ found.add(template)
+ return sorted(found)
+
class PackageLoader(BaseLoader):
"""Load templates from python eggs or packages. It is constructed with
source = self.provider.get_resource_string(self.manager, p)
return source.decode(self.encoding), filename, uptodate
+ def list_templates(self):
+ path = self.package_path
+ if path[:2] == './':
+ path = path[2:]
+ elif path == '.':
+ path = ''
+ offset = len(path)
+ results = []
+ def _walk(path):
+ for filename in self.provider.resource_listdir(path):
+ fullname = path + '/' + filename
+ if self.provider.resource_isdir(fullname):
+ _walk(fullname)
+ else:
+ results.append(fullname[offset:].lstrip('/'))
+ _walk(path)
+ results.sort()
+ return results
+
class DictLoader(BaseLoader):
"""Loads a template from a python dict. It's passed a dict of unicode
return source, None, lambda: source != self.mapping.get(template)
raise TemplateNotFound(template)
+ def list_templates(self):
+ return sorted(self.mapping)
+
class FunctionLoader(BaseLoader):
"""A loader that is passed a function which does the loading. The
# (the one that includes the prefix)
raise TemplateNotFound(template)
+ def list_templates(self):
+ result = []
+ for prefix, loader in self.mapping.iteritems():
+ for template in loader.list_templates():
+ result.append(prefix + self.delimiter + template)
+ return result
+
class ChoiceLoader(BaseLoader):
"""This loader works like the `PrefixLoader` just that no prefix is
except TemplateNotFound:
pass
raise TemplateNotFound(template)
+
+ def list_templates(self):
+ found = set()
+ for loader in self.loaders:
+ found.update(loader.list_templates())
+ return sorted(found)
+
+
+class _TemplateModule(ModuleType):
+ """Like a normal module but with support for weak references"""
+
+
+class ModuleLoader(BaseLoader):
+ """This loader loads templates from precompiled templates.
+
+ Example usage:
+
+ >>> loader = ChoiceLoader([
+ ... ModuleLoader('/path/to/compiled/templates'),
+ ... FileSystemLoader('/path/to/templates')
+ ... ])
+
+ Templates can be precompiled with :meth:`Environment.compile_templates`.
+ """
+
+ has_source_access = False
+
+ def __init__(self, path):
+ package_name = '_jinja2_module_templates_%x' % id(self)
+
+ # create a fake module that looks for the templates in the
+ # path given.
+ mod = _TemplateModule(package_name)
+ if isinstance(path, basestring):
+ path = [path]
+ else:
+ path = list(path)
+ mod.__path__ = path
+
+ sys.modules[package_name] = weakref.proxy(mod,
+ lambda x: sys.modules.pop(package_name, None))
+
+ # the only strong reference, the sys.modules entry is weak
+ # so that the garbage collector can remove it once the
+ # loader that created it goes out of business.
+ self.module = mod
+ self.package_name = package_name
+
+ @staticmethod
+ def get_template_key(name):
+ return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
+
+ @staticmethod
+ def get_module_filename(name):
+ return ModuleLoader.get_template_key(name) + '.py'
+
+ @internalcode
+ def load(self, environment, name, globals=None):
+ key = self.get_template_key(name)
+ module = '%s.%s' % (self.package_name, key)
+ mod = getattr(self.module, module, None)
+ if mod is None:
+ try:
+ mod = __import__(module, None, None, ['root'])
+ except ImportError:
+ raise TemplateNotFound(name)
+
+ # remove the entry from sys.modules, we only want the attribute
+ # on the module object we have stored on the loader.
+ sys.modules.pop(module, None)
+
+ return environment.template_class.from_module_dict(
+ environment, mod.__dict__, globals)
import operator
from itertools import chain, izip
from collections import deque
-from jinja2.utils import Markup
+from jinja2.utils import Markup, MethodType, FunctionType
+
+
+#: the types we support for context functions
+_context_function_types = (FunctionType, MethodType)
_binop_to_func = {
return type.__new__(cls, name, bases, d)
+class EvalContext(object):
+ """Holds evaluation time information. Custom attributes can be attached
+ to it in extensions.
+ """
+
+ def __init__(self, environment, template_name=None):
+ self.environment = environment
+ if callable(environment.autoescape):
+ self.autoescape = environment.autoescape(template_name)
+ else:
+ self.autoescape = environment.autoescape
+ self.volatile = False
+
+ def save(self):
+ return self.__dict__.copy()
+
+ def revert(self, old):
+ self.__dict__.clear()
+ self.__dict__.update(old)
+
+
+def get_eval_context(node, ctx):
+ if ctx is None:
+ if node.environment is None:
+ raise RuntimeError('if no eval context is passed, the '
+ 'node must have an attached '
+ 'environment.')
+ return EvalContext(node.environment)
+ return ctx
+
+
class Node(object):
"""Baseclass for all Jinja2 nodes. There are a number of nodes available
of different types. There are three major types:
"""Baseclass for all expressions."""
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
"""Return the value of the expression as constant or raise
- :exc:`Impossible` if this was not possible:
+ :exc:`Impossible` if this was not possible.
- >>> Add(Const(23), Const(42)).as_const()
- 65
- >>> Add(Const(23), Name('var', 'load')).as_const()
- Traceback (most recent call last):
- ...
- Impossible
+ An :class:`EvalContext` can be provided, if none is given
+ a default context is created which requires the nodes to have
+ an attached environment.
- This requires the `environment` attribute of all nodes to be
- set to the environment that created the nodes.
+ .. versionchanged:: 2.4
+ the `eval_ctx` parameter was added.
"""
raise Impossible()
operator = None
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ # intercepted operators cannot be folded at compile time
+ if self.environment.sandboxed and \
+ self.operator in self.environment.intercepted_binops:
+ raise Impossible()
f = _binop_to_func[self.operator]
try:
- return f(self.left.as_const(), self.right.as_const())
- except:
+ return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
+ except Exception:
raise Impossible()
operator = None
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ # intercepted operators cannot be folded at compile time
+ if self.environment.sandboxed and \
+ self.operator in self.environment.intercepted_unops:
+ raise Impossible()
f = _uaop_to_func[self.operator]
try:
- return f(self.node.as_const())
- except:
+ return f(self.node.as_const(eval_ctx))
+ except Exception:
raise Impossible()
"""
fields = ('value',)
- def as_const(self):
+ def as_const(self, eval_ctx=None):
return self.value
@classmethod
"""A constant template string."""
fields = ('data',)
- def as_const(self):
- if self.environment.autoescape:
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile:
+ raise Impossible()
+ if eval_ctx.autoescape:
return Markup(self.data)
return self.data
"""
fields = ('items', 'ctx')
- def as_const(self):
- return tuple(x.as_const() for x in self.items)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return tuple(x.as_const(eval_ctx) for x in self.items)
def can_assign(self):
for item in self.items:
"""Any list literal such as ``[1, 2, 3]``"""
fields = ('items',)
- def as_const(self):
- return [x.as_const() for x in self.items]
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return [x.as_const(eval_ctx) for x in self.items]
class Dict(Literal):
"""
fields = ('items',)
- def as_const(self):
- return dict(x.as_const() for x in self.items)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return dict(x.as_const(eval_ctx) for x in self.items)
class Pair(Helper):
"""A key, value pair for dicts."""
fields = ('key', 'value')
- def as_const(self):
- return self.key.as_const(), self.value.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
class Keyword(Helper):
"""A key, value pair for keyword arguments where key is a string."""
fields = ('key', 'value')
- def as_const(self):
- return self.key, self.value.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.key, self.value.as_const(eval_ctx)
class CondExpr(Expr):
"""
fields = ('test', 'expr1', 'expr2')
- def as_const(self):
- if self.test.as_const():
- return self.expr1.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if self.test.as_const(eval_ctx):
+ return self.expr1.as_const(eval_ctx)
# if we evaluate to an undefined object, we better do that at runtime
if self.expr2 is None:
raise Impossible()
- return self.expr2.as_const()
+ return self.expr2.as_const(eval_ctx)
class Filter(Expr):
"""
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self, obj=None):
- if self.node is obj is None:
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile or self.node is None:
raise Impossible()
# we have to be careful here because we call filter_ below.
# if this variable would be called filter, 2to3 would wrap the
filter_ = self.environment.filters.get(self.name)
if filter_ is None or getattr(filter_, 'contextfilter', False):
raise Impossible()
- if obj is None:
- obj = self.node.as_const()
- args = [x.as_const() for x in self.args]
- if getattr(filter_, 'environmentfilter', False):
+ obj = self.node.as_const(eval_ctx)
+ args = [x.as_const(eval_ctx) for x in self.args]
+ if getattr(filter_, 'evalcontextfilter', False):
+ args.insert(0, eval_ctx)
+ elif getattr(filter_, 'environmentfilter', False):
args.insert(0, self.environment)
- kwargs = dict(x.as_const() for x in self.kwargs)
+ kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
if self.dyn_args is not None:
try:
- args.extend(self.dyn_args.as_const())
- except:
+ args.extend(self.dyn_args.as_const(eval_ctx))
+ except Exception:
raise Impossible()
if self.dyn_kwargs is not None:
try:
- kwargs.update(self.dyn_kwargs.as_const())
- except:
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
+ except Exception:
raise Impossible()
try:
return filter_(obj, *args, **kwargs)
- except:
+ except Exception:
raise Impossible()
"""
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self):
- obj = self.node.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile:
+ raise Impossible()
+ obj = self.node.as_const(eval_ctx)
# don't evaluate context functions
- args = [x.as_const() for x in self.args]
- if getattr(obj, 'contextfunction', False):
- raise Impossible()
- elif getattr(obj, 'environmentfunction', False):
- args.insert(0, self.environment)
+ args = [x.as_const(eval_ctx) for x in self.args]
+ if isinstance(obj, _context_function_types):
+ if getattr(obj, 'contextfunction', False):
+ raise Impossible()
+ elif getattr(obj, 'evalcontextfunction', False):
+ args.insert(0, eval_ctx)
+ elif getattr(obj, 'environmentfunction', False):
+ args.insert(0, self.environment)
- kwargs = dict(x.as_const() for x in self.kwargs)
+ kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
if self.dyn_args is not None:
try:
- args.extend(self.dyn_args.as_const())
- except:
+ args.extend(self.dyn_args.as_const(eval_ctx))
+ except Exception:
raise Impossible()
if self.dyn_kwargs is not None:
try:
- kwargs.update(self.dyn_kwargs.as_const())
- except:
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
+ except Exception:
raise Impossible()
try:
return obj(*args, **kwargs)
- except:
+ except Exception:
raise Impossible()
"""Get an attribute or item from an expression and prefer the item."""
fields = ('node', 'arg', 'ctx')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
if self.ctx != 'load':
raise Impossible()
try:
- return self.environment.getitem(self.node.as_const(),
- self.arg.as_const())
- except:
+ return self.environment.getitem(self.node.as_const(eval_ctx),
+ self.arg.as_const(eval_ctx))
+ except Exception:
raise Impossible()
def can_assign(self):
"""
fields = ('node', 'attr', 'ctx')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
if self.ctx != 'load':
raise Impossible()
try:
- return self.environment.getattr(self.node.as_const(), arg)
- except:
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.environment.getattr(self.node.as_const(eval_ctx),
+ self.attr)
+ except Exception:
raise Impossible()
def can_assign(self):
"""
fields = ('start', 'stop', 'step')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
def const(obj):
if obj is None:
- return obj
- return obj.as_const()
+ return None
+ return obj.as_const(eval_ctx)
return slice(const(self.start), const(self.stop), const(self.step))
"""
fields = ('nodes',)
- def as_const(self):
- return ''.join(unicode(x.as_const()) for x in self.nodes)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes)
class Compare(Expr):
"""
fields = ('expr', 'ops')
- def as_const(self):
- result = value = self.expr.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ result = value = self.expr.as_const(eval_ctx)
try:
for op in self.ops:
- new_value = op.expr.as_const()
+ new_value = op.expr.as_const(eval_ctx)
result = _cmpop_to_func[op.op](value, new_value)
value = new_value
- except:
+ except Exception:
raise Impossible()
return result
"""Short circuited AND."""
operator = 'and'
- def as_const(self):
- return self.left.as_const() and self.right.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
class Or(BinExpr):
"""Short circuited OR."""
operator = 'or'
- def as_const(self):
- return self.left.as_const() or self.right.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
class Not(UnaryExpr):
yourself but the parser provides a
:meth:`~jinja2.parser.Parser.free_identifier` method that creates
a new identifier for you. This identifier is not available from the
- template and is not treated specially by the compiler.
+ template and is not threated specially by the compiler.
"""
fields = ('name',)
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
fields = ('expr',)
- def as_const(self):
- return Markup(self.expr.as_const())
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return Markup(self.expr.as_const(eval_ctx))
+
+
+class MarkSafeIfAutoescape(Expr):
+ """Mark the wrapped expression as safe (wrap it as `Markup`) but
+ only if autoescaping is active.
+
+ .. versionadded:: 2.5
+ """
+ fields = ('expr',)
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile:
+ raise Impossible()
+ expr = self.expr.as_const(eval_ctx)
+ if eval_ctx.autoescape:
+ return Markup(expr)
+ return expr
class ContextReference(Expr):
- """Returns the current template context."""
+ """Returns the current template context. It can be used like a
+ :class:`Name` node, with a ``'load'`` ctx and will return the
+ current :class:`~jinja2.runtime.Context` object.
+
+ Here an example that assigns the current template name to a
+ variable named `foo`::
+
+ Assign(Name('foo', ctx='store'),
+ Getattr(ContextReference(), 'name'))
+ """
class Continue(Stmt):
fields = ('body',)
+class EvalContextModifier(Stmt):
+ """Modifies the eval context. For each option that should be modified,
+ a :class:`Keyword` has to be added to the :attr:`options` list.
+
+ Example to change the `autoescape` setting::
+
+ EvalContextModifier(options=[Keyword('autoescape', Const(True))])
+ """
+ fields = ('options',)
+
+
+class ScopedEvalContextModifier(EvalContextModifier):
+ """Modifies the eval context and reverts it later. Works exactly like
+ :class:`EvalContextModifier` but will only modify the
+ :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
+ """
+ fields = ('body',)
+
+
# make sure nobody creates custom nodes
def _failing_new(*args, **kwargs):
raise TypeError('can\'t create custom node types')
self.filename = filename
self.closed = False
self.extensions = {}
- for extension in environment.extensions.itervalues():
+ for extension in environment.iter_extensions():
for tag in extension.tags:
self.extensions[tag] = extension.parse
self._last_identifier = 0
target = self.parse_tuple(simplified=True,
extra_end_rules=extra_end_rules)
else:
- target = self.parse_primary(with_postfix=False)
+ target = self.parse_primary()
target.set_ctx('store')
if not target.can_assign():
self.fail('can\'t assign to %r' % target.__class__.
lineno = self.stream.current.lineno
return left
- def parse_unary(self):
+ def parse_unary(self, with_filter=True):
token_type = self.stream.current.type
lineno = self.stream.current.lineno
if token_type == 'sub':
next(self.stream)
- node = self.parse_unary()
- return nodes.Neg(node, lineno=lineno)
- if token_type == 'add':
+ node = nodes.Neg(self.parse_unary(False), lineno=lineno)
+ elif token_type == 'add':
next(self.stream)
- node = self.parse_unary()
- return nodes.Pos(node, lineno=lineno)
- return self.parse_primary()
+ node = nodes.Pos(self.parse_unary(False), lineno=lineno)
+ else:
+ node = self.parse_primary()
+ node = self.parse_postfix(node)
+ if with_filter:
+ node = self.parse_filter_expr(node)
+ return node
- def parse_primary(self, with_postfix=True):
+ def parse_primary(self):
token = self.stream.current
if token.type == 'name':
if token.value in ('true', 'false', 'True', 'False'):
node = self.parse_dict()
else:
self.fail("unexpected '%s'" % describe_token(token), token.lineno)
- if with_postfix:
- node = self.parse_postfix(node)
return node
def parse_tuple(self, simplified=False, with_condexpr=True,
"""
lineno = self.stream.current.lineno
if simplified:
- parse = lambda: self.parse_primary(with_postfix=False)
+ parse = self.parse_primary
elif with_condexpr:
parse = self.parse_expression
else:
token_type = self.stream.current.type
if token_type == 'dot' or token_type == 'lbracket':
node = self.parse_subscript(node)
+ # calls are valid both after postfix expressions (getattr
+ # and getitem) as well as filters and tests
elif token_type == 'lparen':
node = self.parse_call(node)
- elif token_type == 'pipe':
+ else:
+ break
+ return node
+
+ def parse_filter_expr(self, node):
+ while 1:
+ token_type = self.stream.current.type
+ if token_type == 'pipe':
node = self.parse_filter(node)
elif token_type == 'name' and self.stream.current.value == 'is':
node = self.parse_test(node)
+ # calls are valid both after postfix expressions (getattr
+ # and getitem) as well as filters and tests
+ elif token_type == 'lparen':
+ node = self.parse_call(node)
else:
break
return node
:copyright: (c) 2010 by the Jinja Team.
:license: BSD.
"""
-import sys
from itertools import chain, imap
+from jinja2.nodes import EvalContext, _context_function_types
from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
- concat, MethodType, FunctionType, internalcode, next
+ concat, internalcode, next, object_type_repr
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
TemplateNotFound
# these variables are exported to the template runtime
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
'TemplateRuntimeError', 'missing', 'concat', 'escape',
- 'markup_join', 'unicode_join', 'to_string',
+ 'markup_join', 'unicode_join', 'to_string', 'identity',
'TemplateNotFound']
-
-#: the types we support for context functions
-_context_function_types = (FunctionType, MethodType)
-
#: the name of the function that is used to convert something into
#: a string. 2to3 will adopt that automatically and the generated
#: code can take advantage of it.
to_string = unicode
+#: the identity function. Useful for certain things in the environment
+identity = lambda x: x
+
def markup_join(seq):
"""Concatenation that escapes if necessary and converts to unicode."""
def __getitem__(self, name):
blocks = self.__context.blocks[name]
- wrap = self.__context.environment.autoescape and \
- Markup or (lambda x: x)
return BlockReference(name, self.__context, blocks, 0)
def __repr__(self):
method that doesn't fail with a `KeyError` but returns an
:class:`Undefined` object for missing variables.
"""
- __slots__ = ('parent', 'vars', 'environment', 'exported_vars', 'name',
- 'blocks', '__weakref__')
+ __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
+ 'name', 'blocks', '__weakref__')
def __init__(self, environment, parent, name, blocks):
self.parent = parent
self.vars = {}
self.environment = environment
+ self.eval_ctx = EvalContext(self.environment, name)
self.exported_vars = set()
self.name = name
if isinstance(__obj, _context_function_types):
if getattr(__obj, 'contextfunction', 0):
args = (__self,) + args
+ elif getattr(__obj, 'evalcontextfunction', 0):
+ args = (__self.eval_ctx,) + args
elif getattr(__obj, 'environmentfunction', 0):
args = (__self.environment,) + args
- return __obj(*args, **kwargs)
+ try:
+ return __obj(*args, **kwargs)
+ except StopIteration:
+ return __self.environment.undefined('value was undefined because '
+ 'a callable raised a '
+ 'StopIteration exception')
def derived(self, locals=None):
"""Internal helper function to create a derived context."""
context = new_context(self.environment, self.name, {},
self.parent, True, None, locals)
+ context.vars.update(self.vars)
+ context.eval_ctx = self.eval_ctx
context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
return context
@internalcode
def __call__(self):
rv = concat(self._stack[self._depth](self._context))
- if self._context.environment.autoescape:
+ if self._context.eval_ctx.autoescape:
rv = Markup(rv)
return rv
# a nifty trick to enhance the error message if someone tried to call
# the the loop without or with too many arguments.
- __call__ = loop; del loop
+ __call__ = loop
+ del loop
@property
def length(self):
class Macro(object):
- """Wraps a macro."""
+ """Wraps a macro function."""
def __init__(self, environment, func, name, arguments, defaults,
catch_kwargs, catch_varargs, caller):
@internalcode
def __call__(self, *args, **kwargs):
- arguments = []
- for idx, name in enumerate(self.arguments):
- try:
- value = args[idx]
- except:
+ # try to consume the positional arguments
+ arguments = list(args[:self._argument_count])
+ off = len(arguments)
+
+ # if the number of arguments consumed is not the number of
+ # arguments expected we start filling in keyword arguments
+ # and defaults.
+ if off != self._argument_count:
+ for idx, name in enumerate(self.arguments[len(arguments):]):
try:
value = kwargs.pop(name)
- except:
+ except KeyError:
try:
- value = self.defaults[idx - self._argument_count]
- except:
+ value = self.defaults[idx - self._argument_count + off]
+ except IndexError:
value = self._environment.undefined(
'parameter %r was not provided' % name, name=name)
- arguments.append(value)
+ arguments.append(value)
# it's important that the order of these arguments does not change
# if not also changed in the compiler's `function_scoping` method.
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
'_undefined_exception')
- def __init__(self, hint=None, obj=None, name=None, exc=UndefinedError):
+ def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
self._undefined_hint = hint
self._undefined_obj = obj
self._undefined_name = name
`UndefinedError` on call.
"""
if self._undefined_hint is None:
- if self._undefined_obj is None:
+ if self._undefined_obj is missing:
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__,
+ hint = '%s has no element %r' % (
+ object_type_repr(self._undefined_obj),
self._undefined_name
)
else:
- hint = '%r object has no attribute %r' % (
- self._undefined_obj.__class__.__name__,
+ hint = '%r has no attribute %r' % (
+ object_type_repr(self._undefined_obj),
self._undefined_name
)
else:
hint = self._undefined_hint
raise self._undefined_exception(hint)
+ @internalcode
+ def __getattr__(self, name):
+ if name[:2] == '__':
+ raise AttributeError(name)
+ return self._fail_with_undefined_error()
+
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
- __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \
- __int__ = __float__ = __complex__ = __pow__ = __rpow__ = \
+ __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
+ __float__ = __complex__ = __pow__ = __rpow__ = \
_fail_with_undefined_error
def __str__(self):
def __unicode__(self):
if self._undefined_hint is None:
- if self._undefined_obj is None:
+ if self._undefined_obj is missing:
return u'{{ %s }}' % self._undefined_name
return '{{ no such element: %s[%r] }}' % (
- self._undefined_obj.__class__.__name__,
+ object_type_repr(self._undefined_obj),
self._undefined_name
)
return u'{{ undefined value printed: %s }}' % self._undefined_hint
"""
__slots__ = ()
__iter__ = __unicode__ = __str__ = __len__ = __nonzero__ = __eq__ = \
- __ne__ = Undefined._fail_with_undefined_error
+ __ne__ = __bool__ = Undefined._fail_with_undefined_error
# remove remaining slots attributes, after the metaclass did the magic they
:license: BSD.
"""
import operator
-from jinja2.runtime import Undefined
from jinja2.environment import Environment
from jinja2.exceptions import SecurityError
from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
def unsafe(f):
- """
- Mark a function or method as unsafe::
+ """Marks a function or method as unsafe.
+
+ ::
@unsafe
def delete(self):
"""
sandboxed = True
+ #: default callback table for the binary operators. A copy of this is
+ #: available on each instance of a sandboxed environment as
+ #: :attr:`binop_table`
+ default_binop_table = {
+ '+': operator.add,
+ '-': operator.sub,
+ '*': operator.mul,
+ '/': operator.truediv,
+ '//': operator.floordiv,
+ '**': operator.pow,
+ '%': operator.mod
+ }
+
+ #: default callback table for the unary operators. A copy of this is
+ #: available on each instance of a sandboxed environment as
+ #: :attr:`unop_table`
+ default_unop_table = {
+ '+': operator.pos,
+ '-': operator.neg
+ }
+
+ #: a set of binary operators that should be intercepted. Each operator
+ #: that is added to this set (empty by default) is delegated to the
+ #: :meth:`call_binop` method that will perform the operator. The default
+ #: operator callback is specified by :attr:`binop_table`.
+ #:
+ #: The following binary operators are interceptable:
+ #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
+ #:
+ #: The default operation form the operator table corresponds to the
+ #: builtin function. Intercepted calls are always slower than the native
+ #: operator call, so make sure only to intercept the ones you are
+ #: interested in.
+ #:
+ #: .. versionadded:: 2.6
+ intercepted_binops = frozenset()
+
+ #: a set of unary operators that should be intercepted. Each operator
+ #: that is added to this set (empty by default) is delegated to the
+ #: :meth:`call_unop` method that will perform the operator. The default
+ #: operator callback is specified by :attr:`unop_table`.
+ #:
+ #: The following unary operators are interceptable: ``+``, ``-``
+ #:
+ #: The default operation form the operator table corresponds to the
+ #: builtin function. Intercepted calls are always slower than the native
+ #: operator call, so make sure only to intercept the ones you are
+ #: interested in.
+ #:
+ #: .. versionadded:: 2.6
+ intercepted_unops = frozenset()
+
+ def intercept_unop(self, operator):
+ """Called during template compilation with the name of a unary
+ operator to check if it should be intercepted at runtime. If this
+ method returns `True`, :meth:`call_unop` is excuted for this unary
+ operator. The default implementation of :meth:`call_unop` will use
+ the :attr:`unop_table` dictionary to perform the operator with the
+ same logic as the builtin one.
+
+ The following unary operators are interceptable: ``+`` and ``-``
+
+ Intercepted calls are always slower than the native operator call,
+ so make sure only to intercept the ones you are interested in.
+
+ .. versionadded:: 2.6
+ """
+ return False
+
+
def __init__(self, *args, **kwargs):
Environment.__init__(self, *args, **kwargs)
self.globals['range'] = safe_range
+ self.binop_table = self.default_binop_table.copy()
+ self.unop_table = self.default_unop_table.copy()
def is_safe_attribute(self, obj, attr, value):
"""The sandboxed environment will call this method to check if the
True. Override this method to alter the behavior, but this won't
affect the `unsafe` decorator from this module.
"""
- return not (getattr(obj, 'unsafe_callable', False) or \
+ return not (getattr(obj, 'unsafe_callable', False) or
getattr(obj, 'alters_data', False))
+ def call_binop(self, context, operator, left, right):
+ """For intercepted binary operator calls (:meth:`intercepted_binops`)
+ this function is executed instead of the builtin operator. This can
+ be used to fine tune the behavior of certain operators.
+
+ .. versionadded:: 2.6
+ """
+ return self.binop_table[operator](left, right)
+
+ def call_unop(self, context, operator, arg):
+ """For intercepted unary operator calls (:meth:`intercepted_unops`)
+ this function is executed instead of the builtin operator. This can
+ be used to fine tune the behavior of certain operators.
+
+ .. versionadded:: 2.6
+ """
+ return self.unop_table[operator](arg)
+
def getitem(self, obj, argument):
"""Subscribe an object from sandboxed code."""
try:
if isinstance(argument, basestring):
try:
attr = str(argument)
- except:
+ except Exception:
pass
else:
try:
import re
from jinja2.runtime import Undefined
+try:
+ from collections import Mapping as MappingType
+except ImportError:
+ import UserDict
+ MappingType = (UserDict.UserDict, UserDict.DictMixin, dict)
+
# nose, nothing here to test
__test__ = False
return isinstance(value, basestring)
+def test_mapping(value):
+ """Return true if the object is a mapping (dict etc.).
+
+ .. versionadded:: 2.6
+ """
+ return isinstance(value, MappingType)
+
+
def test_number(value):
"""Return true if the variable is a number."""
return isinstance(value, (int, long, float, complex))
'lower': test_lower,
'upper': test_upper,
'string': test_string,
+ 'mapping': test_mapping,
'number': test_number,
'sequence': test_sequence,
'iterable': test_iterable,
def concat(gen):
try:
return _concat(list(gen))
- except:
+ except Exception:
# this hack is needed so that the current frame
# does not show up in the traceback.
exc_type, exc_value, tb = sys.exc_info()
return f
+def evalcontextfunction(f):
+ """This decoraotr can be used to mark a function or method as an eval
+ context callable. This is similar to the :func:`contextfunction`
+ but instead of passing the context, an evaluation context object is
+ passed. For more information about the eval context, see
+ :ref:`eval-context`.
+
+ .. versionadded:: 2.4
+ """
+ f.evalcontextfunction = True
+ return f
+
+
def environmentfunction(f):
"""This decorator can be used to mark a function or method as environment
callable. This decorator works exactly like the :func:`contextfunction`
raise
+def object_type_repr(obj):
+ """Returns the name of the object's type. For some recognized
+ singletons the name of the object is returned instead. (For
+ example for `None` and `Ellipsis`).
+ """
+ if obj is None:
+ return 'None'
+ elif obj is Ellipsis:
+ return 'Ellipsis'
+ # __builtin__ in 2.x, builtins in 3.x
+ if obj.__class__.__module__ in ('__builtin__', 'builtins'):
+ name = obj.__class__.__name__
+ else:
+ name = obj.__class__.__module__ + '.' + obj.__class__.__name__
+ return '%s object' % name
+
+
def pformat(obj, verbose=False):
"""Prettyprint an object. Either use the `pretty` library or the
builtin `pprint`.
return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
-class Markup(unicode):
- r"""Marks a string as being safe for inclusion in HTML/XML output without
- needing to be escaped. This implements the `__html__` interface a couple
- of frameworks and web applications use. :class:`Markup` is a direct
- subclass of `unicode` and provides all the methods of `unicode` just that
- it escapes arguments passed and always returns `Markup`.
-
- The `escape` function returns markup objects so that double escaping can't
- happen. If you want to use autoescaping in Jinja just enable the
- autoescaping feature in the environment.
-
- The constructor of the :class:`Markup` class can be used for three
- different things: When passed an unicode object it's assumed to be safe,
- when passed an object with an HTML representation (has an `__html__`
- method) that representation is used, otherwise the object passed is
- converted into a unicode string and then assumed to be safe:
-
- >>> Markup("Hello <em>World</em>!")
- Markup(u'Hello <em>World</em>!')
- >>> class Foo(object):
- ... def __html__(self):
- ... return '<a href="#">foo</a>'
- ...
- >>> Markup(Foo())
- Markup(u'<a href="#">foo</a>')
-
- If you want object passed being always treated as unsafe you can use the
- :meth:`escape` classmethod to create a :class:`Markup` object:
-
- >>> Markup.escape("Hello <em>World</em>!")
- Markup(u'Hello <em>World</em>!')
-
- Operations on a markup string are markup aware which means that all
- arguments are passed through the :func:`escape` function:
-
- >>> em = Markup("<em>%s</em>")
- >>> em % "foo & bar"
- Markup(u'<em>foo & bar</em>')
- >>> strong = Markup("<strong>%(text)s</strong>")
- >>> strong % {'text': '<blink>hacker here</blink>'}
- Markup(u'<strong><blink>hacker here</blink></strong>')
- >>> Markup("<em>Hello</em> ") + "<foo>"
- Markup(u'<em>Hello</em> <foo>')
- """
- __slots__ = ()
-
- def __new__(cls, base=u'', encoding=None, errors='strict'):
- if hasattr(base, '__html__'):
- base = base.__html__()
- if encoding is None:
- return unicode.__new__(cls, base)
- return unicode.__new__(cls, base, encoding, errors)
-
- def __html__(self):
- return self
-
- def __add__(self, other):
- if hasattr(other, '__html__') or isinstance(other, basestring):
- return self.__class__(unicode(self) + unicode(escape(other)))
- return NotImplemented
-
- def __radd__(self, other):
- if hasattr(other, '__html__') or isinstance(other, basestring):
- return self.__class__(unicode(escape(other)) + unicode(self))
- return NotImplemented
-
- def __mul__(self, num):
- if isinstance(num, (int, long)):
- return self.__class__(unicode.__mul__(self, num))
- return NotImplemented
- __rmul__ = __mul__
-
- def __mod__(self, arg):
- if isinstance(arg, tuple):
- arg = tuple(imap(_MarkupEscapeHelper, arg))
- else:
- arg = _MarkupEscapeHelper(arg)
- return self.__class__(unicode.__mod__(self, arg))
-
- def __repr__(self):
- return '%s(%s)' % (
- self.__class__.__name__,
- unicode.__repr__(self)
- )
-
- def join(self, seq):
- return self.__class__(unicode.join(self, imap(escape, seq)))
- join.__doc__ = unicode.join.__doc__
-
- def split(self, *args, **kwargs):
- return map(self.__class__, unicode.split(self, *args, **kwargs))
- split.__doc__ = unicode.split.__doc__
-
- def rsplit(self, *args, **kwargs):
- return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
- rsplit.__doc__ = unicode.rsplit.__doc__
-
- def splitlines(self, *args, **kwargs):
- return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
- splitlines.__doc__ = unicode.splitlines.__doc__
-
- def unescape(self):
- r"""Unescape markup again into an unicode string. This also resolves
- known HTML4 and XHTML entities:
-
- >>> Markup("Main » <em>About</em>").unescape()
- u'Main \xbb <em>About</em>'
- """
- from jinja2.constants import HTML_ENTITIES
- def handle_match(m):
- name = m.group(1)
- if name in HTML_ENTITIES:
- return unichr(HTML_ENTITIES[name])
- try:
- if name[:2] in ('#x', '#X'):
- return unichr(int(name[2:], 16))
- elif name.startswith('#'):
- return unichr(int(name[1:]))
- except ValueError:
- pass
- return u''
- return _entity_re.sub(handle_match, unicode(self))
-
- def striptags(self):
- r"""Unescape markup into an unicode string and strip all tags. This
- also resolves known HTML4 and XHTML entities. Whitespace is
- normalized to one:
-
- >>> Markup("Main » <em>About</em>").striptags()
- u'Main \xbb About'
- """
- stripped = u' '.join(_striptags_re.sub('', self).split())
- return Markup(stripped).unescape()
-
- @classmethod
- def escape(cls, s):
- """Escape the string. Works like :func:`escape` with the difference
- that for subclasses of :class:`Markup` this function would return the
- correct subclass.
- """
- rv = escape(s)
- if rv.__class__ is not cls:
- return cls(rv)
- return rv
-
- def make_wrapper(name):
- orig = getattr(unicode, name)
- def func(self, *args, **kwargs):
- args = _escape_argspec(list(args), enumerate(args))
- _escape_argspec(kwargs, kwargs.iteritems())
- return self.__class__(orig(self, *args, **kwargs))
- func.__name__ = orig.__name__
- func.__doc__ = orig.__doc__
- return func
-
- for method in '__getitem__', 'capitalize', \
- 'title', 'lower', 'upper', 'replace', 'ljust', \
- 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
- 'translate', 'expandtabs', 'swapcase', 'zfill':
- locals()[method] = make_wrapper(method)
-
- # new in python 2.5
- if hasattr(unicode, 'partition'):
- partition = make_wrapper('partition'),
- rpartition = make_wrapper('rpartition')
-
- # new in python 2.6
- if hasattr(unicode, 'format'):
- format = make_wrapper('format')
-
- # not in python 3
- if hasattr(unicode, '__getslice__'):
- __getslice__ = make_wrapper('__getslice__')
-
- del method, make_wrapper
-
-
-def _escape_argspec(obj, iterable):
- """Helper for various string-wrapped functions."""
- for key, value in iterable:
- if hasattr(value, '__html__') or isinstance(value, basestring):
- obj[key] = escape(value)
- return obj
-
-
-class _MarkupEscapeHelper(object):
- """Helper for Markup.__mod__"""
-
- def __init__(self, obj):
- self.obj = obj
-
- __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
- __str__ = lambda s: str(escape(s.obj))
- __unicode__ = lambda s: unicode(escape(s.obj))
- __repr__ = lambda s: str(escape(repr(s.obj)))
- __int__ = lambda s: int(s.obj)
- __float__ = lambda s: float(s.obj)
-
-
class LRUCache(object):
"""A simple LRU Cache implementation."""
return self.sep
-# we have to import it down here as the speedups module imports the
-# markup type which is define above.
+# try markupsafe first, if that fails go with Jinja2's bundled version
+# of markupsafe. Markupsafe was previously Jinja2's implementation of
+# the Markup object but was moved into a separate package in a patchleve
+# release
try:
- from jinja2._speedups import escape, soft_unicode
+ from markupsafe import Markup, escape, soft_unicode
except ImportError:
- def 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. Marks return value as markup string.
- """
- if hasattr(s, '__html__'):
- return s.__html__()
- return Markup(unicode(s)
- .replace('&', '&')
- .replace('>', '>')
- .replace('<', '<')
- .replace("'", ''')
- .replace('"', '"')
- )
-
- def soft_unicode(s):
- """Make a string unicode if it isn't already. That way a markup
- string is not converted back to unicode.
- """
- if not isinstance(s, unicode):
- s = unicode(s)
- return s
+ from jinja2._markupsafe import Markup, escape, soft_unicode
# partials