Commit 6caa200e authored by Matthias Braun's avatar Matthias Braun
Browse files

updated to latest jinja2

parent a8479789
......@@ -27,7 +27,7 @@
:license: BSD, see LICENSE for more details.
"""
__docformat__ = 'restructuredtext en'
__version__ = '2.7.2'
__version__ = '2.8-dev'
# high level interface
from jinja2.environment import Environment, Template
......@@ -42,7 +42,8 @@ from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
MemcachedBytecodeCache
# undefined types
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
make_logging_undefined
# exceptions
from jinja2.exceptions import TemplateError, UndefinedError, \
......@@ -65,5 +66,5 @@ __all__ = [
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
'evalcontextfilter', 'evalcontextfunction'
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
]
......@@ -82,12 +82,6 @@ else:
return filename.encode('utf-8')
return filename
try:
next = next
except NameError:
def next(it):
return it.next()
def with_metaclass(meta, *bases):
# This requires a bit of explanation: the basic idea is to make a
......@@ -109,42 +103,7 @@ def with_metaclass(meta, *bases):
return metaclass('temporary_class', None, {})
try:
from collections import Mapping as mapping_types
except ImportError:
import UserDict
mapping_types = (UserDict.UserDict, UserDict.DictMixin, dict)
# common types. These do exist in the special types module too which however
# does not exist in IronPython out of the box. Also that way we don't have
# to deal with implementation specific stuff here
class _C(object):
def method(self): pass
def _func():
yield None
function_type = type(_func)
generator_type = type(_func())
method_type = type(_C().method)
code_type = type(_C.method.__code__)
try:
raise TypeError()
except TypeError:
_tb = sys.exc_info()[2]
traceback_type = type(_tb)
frame_type = type(_tb.tb_frame)
try:
from urllib.parse import quote_from_bytes as url_quote
except ImportError:
from urllib import quote as url_quote
try:
from thread import allocate_lock
except ImportError:
try:
from threading import Lock as allocate_lock
except ImportError:
from dummy_thread import allocate_lock
......@@ -17,6 +17,7 @@
from os import path, listdir
import os
import sys
import stat
import errno
import marshal
import tempfile
......@@ -87,7 +88,12 @@ class Bucket(object):
if self.checksum != checksum:
self.reset()
return
self.code = marshal_load(f)
# if marshal_load fails then we need to reload
try:
self.code = marshal_load(f)
except (EOFError, ValueError, TypeError):
self.reset()
return
def write_bytecode(self, f):
"""Dump the bytecode into the file or file like object passed."""
......@@ -211,24 +217,43 @@ class FileSystemBytecodeCache(BytecodeCache):
self.pattern = pattern
def _get_default_cache_dir(self):
def _unsafe_dir():
raise RuntimeError('Cannot determine safe temp directory. You '
'need to explicitly provide one.')
tmpdir = tempfile.gettempdir()
# On windows the temporary directory is used specific unless
# explicitly forced otherwise. We can just use that.
if os.name == 'n':
if os.name == 'nt':
return tmpdir
if not hasattr(os, 'getuid'):
raise RuntimeError('Cannot determine safe temp directory. You '
'need to explicitly provide one.')
_unsafe_dir()
dirname = '_jinja2-cache-%d' % os.getuid()
actual_dir = os.path.join(tmpdir, dirname)
try:
# 448 == 0700
os.mkdir(actual_dir, 448)
os.mkdir(actual_dir, stat.S_IRWXU)
except OSError as e:
if e.errno != errno.EEXIST:
raise
try:
os.chmod(actual_dir, stat.S_IRWXU)
actual_dir_stat = os.lstat(actual_dir)
if actual_dir_stat.st_uid != os.getuid() \
or not stat.S_ISDIR(actual_dir_stat.st_mode) \
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
_unsafe_dir()
except OSError as e:
if e.errno != errno.EEXIST:
raise
actual_dir_stat = os.lstat(actual_dir)
if actual_dir_stat.st_uid != os.getuid() \
or not stat.S_ISDIR(actual_dir_stat.st_mode) \
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
_unsafe_dir()
return actual_dir
......
......@@ -16,7 +16,7 @@ from jinja2.nodes import EvalContext
from jinja2.visitor import NodeVisitor
from jinja2.exceptions import TemplateAssertionError
from jinja2.utils import Markup, concat, escape
from jinja2._compat import range_type, next, text_type, string_types, \
from jinja2._compat import range_type, text_type, string_types, \
iteritems, NativeStringIO, imap
......@@ -347,6 +347,9 @@ class FrameIdentifierVisitor(NodeVisitor):
def visit_FilterBlock(self, node):
self.visit(node.filter)
def visit_AssignBlock(self, node):
"""Stop visiting at block assigns."""
def visit_Scope(self, node):
"""Stop visiting at scopes."""
......@@ -1332,42 +1335,62 @@ class CodeGenerator(NodeVisitor):
if outdent_later:
self.outdent()
def visit_Assign(self, node, frame):
self.newline(node)
def make_assignment_frame(self, frame):
# toplevel assignments however go into the local namespace and
# the current template's context. We create a copy of the frame
# here and add a set so that the Name visitor can add the assigned
# names here.
if frame.toplevel:
assignment_frame = frame.copy()
assignment_frame.toplevel_assignments = set()
if not frame.toplevel:
return frame
assignment_frame = frame.copy()
assignment_frame.toplevel_assignments = set()
return assignment_frame
def export_assigned_vars(self, frame, assignment_frame):
if not frame.toplevel:
return
public_names = [x for x in assignment_frame.toplevel_assignments
if not x.startswith('_')]
if len(assignment_frame.toplevel_assignments) == 1:
name = next(iter(assignment_frame.toplevel_assignments))
self.writeline('context.vars[%r] = l_%s' % (name, name))
else:
assignment_frame = frame
self.writeline('context.vars.update({')
for idx, name in enumerate(assignment_frame.toplevel_assignments):
if idx:
self.write(', ')
self.write('%r: l_%s' % (name, name))
self.write('})')
if public_names:
if len(public_names) == 1:
self.writeline('context.exported_vars.add(%r)' %
public_names[0])
else:
self.writeline('context.exported_vars.update((%s))' %
', '.join(imap(repr, public_names)))
def visit_Assign(self, node, frame):
self.newline(node)
assignment_frame = self.make_assignment_frame(frame)
self.visit(node.target, assignment_frame)
self.write(' = ')
self.visit(node.node, frame)
# make sure toplevel assignments are added to the context.
if frame.toplevel:
public_names = [x for x in assignment_frame.toplevel_assignments
if not x.startswith('_')]
if len(assignment_frame.toplevel_assignments) == 1:
name = next(iter(assignment_frame.toplevel_assignments))
self.writeline('context.vars[%r] = l_%s' % (name, name))
else:
self.writeline('context.vars.update({')
for idx, name in enumerate(assignment_frame.toplevel_assignments):
if idx:
self.write(', ')
self.write('%r: l_%s' % (name, name))
self.write('})')
if public_names:
if len(public_names) == 1:
self.writeline('context.exported_vars.add(%r)' %
public_names[0])
else:
self.writeline('context.exported_vars.update((%s))' %
', '.join(imap(repr, public_names)))
self.export_assigned_vars(frame, assignment_frame)
def visit_AssignBlock(self, node, frame):
block_frame = frame.inner()
block_frame.inspect(node.body)
aliases = self.push_scope(block_frame)
self.pull_locals(block_frame)
self.buffer(block_frame)
self.blockvisit(node.body, block_frame)
self.pop_scope(aliases, block_frame)
assignment_frame = self.make_assignment_frame(frame)
self.newline(node)
self.visit(node.target, assignment_frame)
self.write(' = concat(%s)' % block_frame.buffer)
self.export_assigned_vars(frame, assignment_frame)
# -- Expression Visitors
......
......@@ -12,10 +12,10 @@
"""
import sys
import traceback
from types import TracebackType
from types import TracebackType, CodeType
from jinja2.utils import missing, internal_code
from jinja2.exceptions import TemplateSyntaxError
from jinja2._compat import iteritems, reraise, code_type
from jinja2._compat import iteritems, reraise
# on pypy we can take advantage of transparent proxies
try:
......@@ -245,11 +245,11 @@ def fake_exc_info(exc_info, filename, lineno):
location = 'block "%s"' % function[6:]
else:
location = 'template'
code = code_type(0, code.co_nlocals, code.co_stacksize,
code.co_flags, code.co_code, code.co_consts,
code.co_names, code.co_varnames, filename,
location, code.co_firstlineno,
code.co_lnotab, (), ())
code = CodeType(0, code.co_nlocals, code.co_stacksize,
code.co_flags, code.co_code, code.co_consts,
code.co_names, code.co_varnames, filename,
location, code.co_firstlineno,
code.co_lnotab, (), ())
except:
pass
......
......@@ -90,13 +90,13 @@ def load_extensions(environment, extensions):
def _environment_sanity_check(environment):
"""Perform a sanity check on the environment."""
assert issubclass(environment.undefined, Undefined), 'undefined must ' \
'be a subclass of undefined because filters depend on it.'
'be a subclass of undefined because filters depend on it.'
assert environment.block_start_string != \
environment.variable_start_string != \
environment.comment_start_string, 'block, variable and comment ' \
'start strings must be different'
environment.variable_start_string != \
environment.comment_start_string, 'block, variable and comment ' \
'start strings must be different'
assert environment.newline_sequence in ('\r', '\r\n', '\n'), \
'newline_sequence set to unknown line ending string.'
'newline_sequence set to unknown line ending string.'
return environment
......@@ -108,16 +108,16 @@ class Environment(object):
Modifications on environments after the first template was loaded
will lead to surprising effects and undefined behavior.
Here the possible initialization parameters:
Here are the possible initialization parameters:
`block_start_string`
The string marking the begin of a block. Defaults to ``'{%'``.
The string marking the beginning of a block. Defaults to ``'{%'``.
`block_end_string`
The string marking the end of a block. Defaults to ``'%}'``.
`variable_start_string`
The string marking the begin of a print statement.
The string marking the beginning of a print statement.
Defaults to ``'{{'``.
`variable_end_string`
......@@ -125,7 +125,7 @@ class Environment(object):
``'}}'``.
`comment_start_string`
The string marking the begin of a comment. Defaults to ``'{#'``.
The string marking the beginning of a comment. Defaults to ``'{#'``.
`comment_end_string`
The string marking the end of a comment. Defaults to ``'#}'``.
......@@ -180,7 +180,7 @@ class Environment(object):
`autoescape`
If set to true the XML/HTML autoescaping feature is enabled by
default. For more details about auto escaping see
default. For more details about autoescaping 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
......@@ -193,12 +193,15 @@ class Environment(object):
The template loader for this environment.
`cache_size`
The size of the cache. Per default this is ``50`` which means
that if more than 50 templates are loaded the loader will clean
The size of the cache. Per default this is ``400`` which means
that if more than 400 templates are loaded the loader will clean
out the least recently used template. If the cache size is set to
``0`` templates are recompiled all the time, if the cache size is
``-1`` the cache will not be cleaned.
.. versionchanged:: 2.8
The cache size was increased to 400 from a low 50.
`auto_reload`
Some loaders load templates from locations where the template
sources may change (ie: file system or database). If
......@@ -254,7 +257,7 @@ class Environment(object):
finalize=None,
autoescape=False,
loader=None,
cache_size=50,
cache_size=400,
auto_reload=True,
bytecode_cache=None):
# !!Important notice!!
......@@ -330,7 +333,7 @@ class Environment(object):
loader=missing, cache_size=missing, auto_reload=missing,
bytecode_cache=missing):
"""Create a new overlay environment that shares all the data with the
current environment except of cache and the overridden attributes.
current environment except for cache and the overridden attributes.
Extensions cannot be removed for an overlayed environment. An overlayed
environment automatically gets all the extensions of the environment it
is linked to plus optional extra extensions.
......@@ -551,7 +554,7 @@ class Environment(object):
return self._compile(source, filename)
except TemplateSyntaxError:
exc_info = sys.exc_info()
self.handle_exception(exc_info, source_hint=source)
self.handle_exception(exc_info, source_hint=source_hint)
def compile_expression(self, source, undefined_to_none=True):
"""A handy helper method that returns a callable that accepts keyword
......@@ -603,8 +606,8 @@ class Environment(object):
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
zipfile, the templates 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`.
......@@ -634,7 +637,8 @@ class Environment(object):
warn(Warning('py_compile has no effect on pypy or Python 3'))
py_compile = False
else:
import imp, marshal
import imp
import marshal
py_header = imp.get_magic() + \
u'\xff\xff\xff\xff'.encode('iso-8859-15')
......@@ -716,7 +720,7 @@ class Environment(object):
filter_func = lambda x: '.' in x and \
x.rsplit('.', 1)[1] in extensions
if filter_func is not None:
x = ifilter(filter_func, x)
x = list(ifilter(filter_func, x))
return x
def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
......@@ -757,14 +761,23 @@ class Environment(object):
def _load_template(self, name, globals):
if self.loader is None:
raise TypeError('no loader for this environment specified')
try:
# use abs path for cache key
cache_key = self.loader.get_source(self, name)[1]
except RuntimeError:
# if loader does not implement get_source()
cache_key = None
# if template is not file, use name for cache key
if cache_key is None:
cache_key = name
if self.cache is not None:
template = self.cache.get(name)
if template is not None and (not self.auto_reload or \
template = self.cache.get(cache_key)
if template is not None and (not self.auto_reload or
template.is_up_to_date):
return template
template = self.loader.load(self, name, globals)
if self.cache is not None:
self.cache[name] = template
self.cache[cache_key] = template
return template
@internalcode
......@@ -1131,7 +1144,9 @@ class TemplateStream(object):
"""
close = False
if isinstance(fp, string_types):
fp = open(fp, encoding is None and 'w' or 'wb')
if encoding is None:
encoding = 'utf-8'
fp = open(fp, 'wb')
close = True
try:
if encoding is not None:
......
......@@ -20,7 +20,7 @@ from jinja2.environment import Environment
from jinja2.runtime import concat
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
from jinja2.utils import contextfunction, import_string, Markup
from jinja2._compat import next, with_metaclass, string_types, iteritems
from jinja2._compat import with_metaclass, string_types, iteritems
# the only real useful gettext functions for a Jinja template. Note
......
......@@ -18,7 +18,7 @@ from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
unicode_urlencode
from jinja2.runtime import Undefined
from jinja2.exceptions import FilterArgumentError
from jinja2._compat import next, imap, string_types, text_type, iteritems
from jinja2._compat import imap, string_types, text_type, iteritems
_word_re = re.compile(r'\w+(?u)')
......@@ -183,7 +183,7 @@ def do_title(s):
uppercase letters, all remaining characters are lowercase.
"""
rv = []
for item in re.compile(r'([-\s]+)(?u)').split(s):
for item in re.compile(r'([-\s]+)(?u)').split(soft_unicode(s)):
if not item:
continue
rv.append(item[0].upper() + item[1:].lower())
......@@ -204,8 +204,7 @@ def do_dictsort(value, case_sensitive=False, by='key'):
sort the dict by key, case sensitive
{% for item in mydict|dictsort(false, 'value') %}
sort the dict by key, case insensitive, sorted
normally and ordered by value.
sort the dict by value, case insensitive
"""
if by == 'key':
pos = 0
......@@ -409,7 +408,8 @@ def do_pprint(value, verbose=False):
@evalcontextfilter
def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
target=None):
"""Converts URLs in plain text into clickable links.
If you pass the filter an additional integer it will shorten the urls
......@@ -420,8 +420,18 @@ def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
{{ mytext|urlize(40, true) }}
links are shortened to 40 chars and defined with rel="nofollow"
If *target* is specified, the ``target`` attribute will be added to the
``<a>`` tag:
.. sourcecode:: jinja
{{ mytext|urlize(40, target='_blank') }}
.. versionchanged:: 2.8+
The *target* parameter was added.
"""
rv = urlize(value, trim_url_limit, nofollow)
rv = urlize(value, trim_url_limit, nofollow, target)
if eval_ctx.autoescape:
rv = Markup(rv)
return rv
......@@ -456,25 +466,22 @@ def do_truncate(s, length=255, killwords=False, end='...'):
.. sourcecode:: jinja
{{ "foo bar"|truncate(5) }}
{{ "foo bar baz"|truncate(9) }}
-> "foo ba..."
{{ "foo bar baz"|truncate(9, True) }}
-> "foo ..."
{{ "foo bar"|truncate(5, True) }}
-> "foo b..."
"""
if len(s) <= length:
return s
elif killwords:
return s[:length] + end
words = s.split(' ')
result = []
m = 0
for word in words:
m += len(word) + 1
if m > length:
break
result.append(word)
result.append(end)
return u' '.join(result)
return s[:length - len(end)] + end
result = s[:length - len(end)].rsplit(' ', 1)[0]
if len(result) < length:
result += ' '
return result + end
@environmentfilter
def do_wordwrap(environment, s, width=79, break_long_words=True,
......@@ -612,7 +619,6 @@ def do_batch(value, linecount, fill_with=None):
{%- endfor %}
</table>
"""
result = []
tmp = []
for item in value:
if len(tmp) == linecount:
......@@ -842,14 +848,15 @@ def do_map(*args, **kwargs):
@contextfilter
def do_select(*args, **kwargs):
"""Filters a sequence of objects by appying a test to either the object
or the attribute and only selecting the ones with the test succeeding.
"""Filters a sequence of objects by appying a test to the object and only
selecting the ones with the test succeeding.
Example usage:
.. sourcecode:: jinja
{{ numbers|select("odd") }}
{{ numbers|select("odd") }}
.. versionadded:: 2.7
"""
......@@ -858,8 +865,8 @@ def do_select(*args, **kwargs):
@contextfilter
def do_reject(*args, **kwargs):
"""Filters a sequence of objects by appying a test to either the object
or the attribute and rejecting the ones with the test succeeding.
"""Filters a sequence of objects by appying a test to the object and
rejecting the ones with the test succeeding.
Example usage:
......@@ -874,8 +881,8 @@ def do_reject(*args, **kwargs):
@contextfilter
def do_selectattr(*args, **kwargs):
"""Filters a sequence of objects by appying a test to either the object
or the attribute and only selecting the ones with the test succeeding.
"""Filters a sequence of objects by appying a test to an attribute of an
object and only selecting the ones with the test succeeding.
Example usage:
......@@ -891,8 +898,8 @@ def do_selectattr(*args, **kwargs):
@contextfilter
def do_rejectattr(*args, **kwargs):
"""Filters a sequence of objects by appying a test to either the object
or the attribute and rejecting the ones with the test succeeding.
"""Filters a sequence of objects by appying a test to an attribute of an
object or the attribute and rejecting the ones with the test succeeding.
.. sourcecode:: jinja
......
......@@ -20,8 +20,8 @@ from operator import itemgetter
from collections import deque