Translation of UI (german and english)
Bugfix for feeds
- removed categories related and up
- load new books now working
- category random now working
login page is free of non accessible elements
boolean custom column is vivible in UI
books with only with certain languages can be shown
book shelfs can be deleted from UI
Anonymous user view is more resticted
Added browse of series in sidebar
Dependencys in vendor folder are updated to newer versions (licencs files are now present)
Bugfix editing Authors names
Made upload on windows working
This commit is contained in:
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
Jinja is written and maintained by the Jinja Team and various
|
||||
contributors:
|
||||
|
||||
Lead Developer:
|
||||
|
||||
- Armin Ronacher <armin.ronacher@active-4.com>
|
||||
|
||||
Developers:
|
||||
|
||||
- Christoph Hack
|
||||
- Georg Brandl
|
||||
|
||||
Contributors:
|
||||
|
||||
- Bryan McLemore
|
||||
- Mickaël Guérin <kael@crocobox.org>
|
||||
- Cameron Knight
|
||||
- Lawrence Journal-World.
|
||||
- David Cramer
|
||||
|
||||
Patches and suggestions:
|
||||
|
||||
- Ronny Pfannschmidt
|
||||
- Axel Böhm
|
||||
- Alexey Melchakov
|
||||
- Bryan McLemore
|
||||
- Clovis Fabricio (nosklo)
|
||||
- Cameron Knight
|
||||
- Peter van Dijk (Habbie)
|
||||
- Stefan Ebner
|
||||
- Rene Leonhardt
|
||||
- Thomas Waldmann
|
||||
- Cory Benfield (Lukasa)
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+4
-3
@@ -27,7 +27,7 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__version__ = '2.7'
|
||||
__version__ = '2.8'
|
||||
|
||||
# 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',
|
||||
]
|
||||
|
||||
Vendored
+2
-41
@@ -22,6 +22,7 @@ if not PY2:
|
||||
range_type = range
|
||||
text_type = str
|
||||
string_types = (str,)
|
||||
integer_types = (int,)
|
||||
|
||||
iterkeys = lambda d: iter(d.keys())
|
||||
itervalues = lambda d: iter(d.values())
|
||||
@@ -51,6 +52,7 @@ else:
|
||||
text_type = unicode
|
||||
range_type = xrange
|
||||
string_types = (str, unicode)
|
||||
integer_types = (int, long)
|
||||
|
||||
iterkeys = lambda d: d.iterkeys()
|
||||
itervalues = lambda d: d.itervalues()
|
||||
@@ -82,12 +84,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 +105,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
|
||||
|
||||
Vendored
+56
-5
@@ -15,13 +15,16 @@
|
||||
:license: BSD.
|
||||
"""
|
||||
from os import path, listdir
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import errno
|
||||
import marshal
|
||||
import tempfile
|
||||
import fnmatch
|
||||
from hashlib import sha1
|
||||
from jinja2.utils import open_if_exists
|
||||
from jinja2._compat import BytesIO, pickle, PY2
|
||||
from jinja2._compat import BytesIO, pickle, PY2, text_type
|
||||
|
||||
|
||||
# marshal works better on 3.x, one hack less required
|
||||
@@ -85,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."""
|
||||
@@ -160,7 +168,7 @@ class BytecodeCache(object):
|
||||
hash = sha1(name.encode('utf-8'))
|
||||
if filename is not None:
|
||||
filename = '|' + filename
|
||||
if isinstance(filename, unicode):
|
||||
if isinstance(filename, text_type):
|
||||
filename = filename.encode('utf-8')
|
||||
hash.update(filename)
|
||||
return hash.hexdigest()
|
||||
@@ -189,7 +197,9 @@ class FileSystemBytecodeCache(BytecodeCache):
|
||||
two arguments: The directory where the cache items are stored and a
|
||||
pattern string that is used to build the filename.
|
||||
|
||||
If no directory is specified the system temporary items folder is used.
|
||||
If no directory is specified a default cache directory is selected. On
|
||||
Windows the user's temp directory is used, on UNIX systems a directory
|
||||
is created for the user in the system temp directory.
|
||||
|
||||
The pattern can be used to have multiple separate caches operate on the
|
||||
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
|
||||
@@ -202,10 +212,51 @@ class FileSystemBytecodeCache(BytecodeCache):
|
||||
|
||||
def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
|
||||
if directory is None:
|
||||
directory = tempfile.gettempdir()
|
||||
directory = self._get_default_cache_dir()
|
||||
self.directory = directory
|
||||
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 == 'nt':
|
||||
return tmpdir
|
||||
if not hasattr(os, 'getuid'):
|
||||
_unsafe_dir()
|
||||
|
||||
dirname = '_jinja2-cache-%d' % os.getuid()
|
||||
actual_dir = os.path.join(tmpdir, dirname)
|
||||
|
||||
try:
|
||||
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
|
||||
|
||||
def _get_cache_filename(self, bucket):
|
||||
return path.join(self.directory, self.pattern % bucket.key)
|
||||
|
||||
|
||||
Vendored
+79
-40
@@ -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
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ def generate(node, environment, name, filename, stream=None,
|
||||
"""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, defer_init)
|
||||
generator = environment.code_generator_class(environment, name, filename,
|
||||
stream, defer_init)
|
||||
generator.visit(node)
|
||||
if stream is None:
|
||||
return generator.stream.getvalue()
|
||||
@@ -347,6 +348,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."""
|
||||
|
||||
@@ -949,16 +953,9 @@ class CodeGenerator(NodeVisitor):
|
||||
self.indent()
|
||||
|
||||
if node.with_context:
|
||||
self.writeline('include_context = template.new_context('
|
||||
'context.parent, True, locals())')
|
||||
self.writeline('for name, context_blocks in context.'
|
||||
'blocks.%s():' % dict_item_iter)
|
||||
self.indent()
|
||||
self.writeline('include_context.blocks.setdefault('
|
||||
'name, [])[0:0] = context_blocks')
|
||||
self.outdent()
|
||||
self.writeline('for event in template.root_render_func('
|
||||
'include_context):')
|
||||
'template.new_context(context.parent, True, '
|
||||
'locals())):')
|
||||
else:
|
||||
self.writeline('for event in template.module._body_stream:')
|
||||
|
||||
@@ -1222,8 +1219,17 @@ class CodeGenerator(NodeVisitor):
|
||||
if self.has_known_extends and frame.require_output_check:
|
||||
return
|
||||
|
||||
allow_constant_finalize = True
|
||||
if self.environment.finalize:
|
||||
finalize = lambda x: text_type(self.environment.finalize(x))
|
||||
func = self.environment.finalize
|
||||
if getattr(func, 'contextfunction', False) or \
|
||||
getattr(func, 'evalcontextfunction', False):
|
||||
allow_constant_finalize = False
|
||||
elif getattr(func, 'environmentfunction', False):
|
||||
finalize = lambda x: text_type(
|
||||
self.environment.finalize(self.environment, x))
|
||||
else:
|
||||
finalize = lambda x: text_type(self.environment.finalize(x))
|
||||
else:
|
||||
finalize = text_type
|
||||
|
||||
@@ -1239,6 +1245,8 @@ class CodeGenerator(NodeVisitor):
|
||||
body = []
|
||||
for child in node.nodes:
|
||||
try:
|
||||
if not allow_constant_finalize:
|
||||
raise nodes.Impossible()
|
||||
const = child.as_const(frame.eval_ctx)
|
||||
except nodes.Impossible:
|
||||
body.append(child)
|
||||
@@ -1294,6 +1302,9 @@ class CodeGenerator(NodeVisitor):
|
||||
self.write('to_string(')
|
||||
if self.environment.finalize is not None:
|
||||
self.write('environment.finalize(')
|
||||
if getattr(self.environment.finalize,
|
||||
"contextfunction", False):
|
||||
self.write('context, ')
|
||||
close += 1
|
||||
self.visit(item, frame)
|
||||
self.write(')' * close)
|
||||
@@ -1316,7 +1327,6 @@ class CodeGenerator(NodeVisitor):
|
||||
arguments.append(item)
|
||||
self.writeline('yield ')
|
||||
self.write(repr(concat(format)) + ' % (')
|
||||
idx = -1
|
||||
self.indent()
|
||||
for argument in arguments:
|
||||
self.newline(argument)
|
||||
@@ -1330,6 +1340,15 @@ class CodeGenerator(NodeVisitor):
|
||||
close += 1
|
||||
if self.environment.finalize is not None:
|
||||
self.write('environment.finalize(')
|
||||
if getattr(self.environment.finalize,
|
||||
'contextfunction', False):
|
||||
self.write('context, ')
|
||||
elif getattr(self.environment.finalize,
|
||||
'evalcontextfunction', False):
|
||||
self.write('context.eval_ctx, ')
|
||||
elif getattr(self.environment.finalize,
|
||||
'environmentfunction', False):
|
||||
self.write('environment, ')
|
||||
close += 1
|
||||
self.visit(argument, frame)
|
||||
self.write(')' * close + ', ')
|
||||
@@ -1339,42 +1358,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)
|
||||
self.export_assigned_vars(frame, assignment_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)))
|
||||
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
|
||||
|
||||
|
||||
Vendored
+25
-12
@@ -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, PY2
|
||||
|
||||
# on pypy we can take advantage of transparent proxies
|
||||
try:
|
||||
@@ -245,12 +245,21 @@ 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, (), ())
|
||||
except:
|
||||
|
||||
if PY2:
|
||||
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, (), ())
|
||||
else:
|
||||
code = CodeType(0, code.co_kwonlyargcount,
|
||||
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 Exception as e:
|
||||
pass
|
||||
|
||||
# execute the code and catch the new traceback
|
||||
@@ -273,11 +282,15 @@ def _init_ugly_crap():
|
||||
import ctypes
|
||||
from types import TracebackType
|
||||
|
||||
# figure out side of _Py_ssize_t
|
||||
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
|
||||
_Py_ssize_t = ctypes.c_int64
|
||||
if PY2:
|
||||
# figure out size of _Py_ssize_t for Python 2:
|
||||
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
|
||||
_Py_ssize_t = ctypes.c_int64
|
||||
else:
|
||||
_Py_ssize_t = ctypes.c_int
|
||||
else:
|
||||
_Py_ssize_t = ctypes.c_int
|
||||
# platform ssize_t on Python 3
|
||||
_Py_ssize_t = ctypes.c_ssize_t
|
||||
|
||||
# regular python
|
||||
class _PyObject(ctypes.Structure):
|
||||
|
||||
Vendored
+1
-1
@@ -32,7 +32,7 @@ from jinja2.filters import FILTERS as DEFAULT_FILTERS
|
||||
from jinja2.tests import TESTS as DEFAULT_TESTS
|
||||
DEFAULT_NAMESPACE = {
|
||||
'range': range_type,
|
||||
'dict': lambda **kw: kw,
|
||||
'dict': dict,
|
||||
'lipsum': generate_lorem_ipsum,
|
||||
'cycler': Cycler,
|
||||
'joiner': Joiner
|
||||
|
||||
Vendored
+60
-38
@@ -21,8 +21,8 @@ from jinja2.lexer import get_lexer, TokenStream
|
||||
from jinja2.parser import Parser
|
||||
from jinja2.nodes import EvalContext
|
||||
from jinja2.optimizer import optimize
|
||||
from jinja2.compiler import generate
|
||||
from jinja2.runtime import Undefined, new_context
|
||||
from jinja2.compiler import generate, CodeGenerator
|
||||
from jinja2.runtime import Undefined, new_context, Context
|
||||
from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
|
||||
TemplatesNotFound, TemplateRuntimeError
|
||||
from jinja2.utils import import_string, LRUCache, Markup, missing, \
|
||||
@@ -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 ``'#}'``.
|
||||
@@ -136,7 +136,7 @@ class Environment(object):
|
||||
|
||||
`line_comment_prefix`
|
||||
If given and a string, this will be used as prefix for line based
|
||||
based comments. See also :ref:`line-statements`.
|
||||
comments. See also :ref:`line-statements`.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
@@ -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
|
||||
@@ -235,6 +238,14 @@ class Environment(object):
|
||||
exception_handler = None
|
||||
exception_formatter = None
|
||||
|
||||
#: the class that is used for code generation. See
|
||||
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
|
||||
code_generator_class = CodeGenerator
|
||||
|
||||
#: the context class thatis used for templates. See
|
||||
#: :class:`~jinja2.runtime.Context` for more information.
|
||||
context_class = Context
|
||||
|
||||
def __init__(self,
|
||||
block_start_string=BLOCK_START_STRING,
|
||||
block_end_string=BLOCK_END_STRING,
|
||||
@@ -254,7 +265,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 +341,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.
|
||||
@@ -411,7 +422,7 @@ class Environment(object):
|
||||
func = self.filters.get(name)
|
||||
if func is None:
|
||||
raise TemplateRuntimeError('no filter named %r' % name)
|
||||
args = list(args or ())
|
||||
args = [value] + list(args or ())
|
||||
if getattr(func, 'contextfilter', False):
|
||||
if context is None:
|
||||
raise TemplateRuntimeError('Attempted to invoke context '
|
||||
@@ -426,7 +437,7 @@ class Environment(object):
|
||||
args.insert(0, eval_ctx)
|
||||
elif getattr(func, 'environmentfilter', False):
|
||||
args.insert(0, self)
|
||||
return func(value, *args, **(kwargs or {}))
|
||||
return func(*args, **(kwargs or {}))
|
||||
|
||||
def call_test(self, name, value, args=None, kwargs=None):
|
||||
"""Invokes a test on a value the same way the compiler does it.
|
||||
@@ -551,7 +562,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 +614,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 +645,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 +728,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 +769,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
|
||||
@@ -866,13 +887,12 @@ class Template(object):
|
||||
and compatible settings.
|
||||
|
||||
>>> template = Template('Hello {{ name }}!')
|
||||
>>> template.render(name='John Doe')
|
||||
u'Hello John Doe!'
|
||||
|
||||
>>> template.render(name='John Doe') == u'Hello John Doe!'
|
||||
True
|
||||
>>> stream = template.stream(name='John Doe')
|
||||
>>> stream.next()
|
||||
u'Hello John Doe!'
|
||||
>>> stream.next()
|
||||
>>> next(stream) == u'Hello John Doe!'
|
||||
True
|
||||
>>> next(stream)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
StopIteration
|
||||
@@ -1019,10 +1039,10 @@ class Template(object):
|
||||
exported template variables from the Python layer:
|
||||
|
||||
>>> t = Template('{% macro foo() %}42{% endmacro %}23')
|
||||
>>> unicode(t.module)
|
||||
u'23'
|
||||
>>> t.module.foo()
|
||||
u'42'
|
||||
>>> str(t.module)
|
||||
'23'
|
||||
>>> t.module.foo() == u'42'
|
||||
True
|
||||
"""
|
||||
if self._module is not None:
|
||||
return self._module
|
||||
@@ -1131,7 +1151,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:
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
Vendored
+82
-73
@@ -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)')
|
||||
@@ -94,7 +94,8 @@ def do_urlencode(value):
|
||||
if itemiter is None:
|
||||
return unicode_urlencode(value)
|
||||
return u'&'.join(unicode_urlencode(k) + '=' +
|
||||
unicode_urlencode(v) for k, v in itemiter)
|
||||
unicode_urlencode(v, for_qs=True)
|
||||
for k, v in itemiter)
|
||||
|
||||
|
||||
@evalcontextfilter
|
||||
@@ -183,10 +184,10 @@ 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:])
|
||||
rv.append(item[0].upper() + item[1:].lower())
|
||||
return ''.join(rv)
|
||||
|
||||
|
||||
@@ -204,8 +205,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 +409,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 +421,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 +467,22 @@ def do_truncate(s, length=255, killwords=False, end='...'):
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ "foo bar"|truncate(5) }}
|
||||
{{ "foo bar baz"|truncate(9) }}
|
||||
-> "foo ..."
|
||||
{{ "foo bar"|truncate(5, True) }}
|
||||
-> "foo b..."
|
||||
{{ "foo bar baz"|truncate(9, True) }}
|
||||
-> "foo ba..."
|
||||
|
||||
"""
|
||||
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,
|
||||
@@ -503,13 +511,16 @@ def do_wordcount(s):
|
||||
return len(_word_re.findall(s))
|
||||
|
||||
|
||||
def do_int(value, default=0):
|
||||
def do_int(value, default=0, base=10):
|
||||
"""Convert the value into an integer. If the
|
||||
conversion doesn't work it will return ``0``. You can
|
||||
override this default using the first parameter.
|
||||
override this default using the first parameter. You
|
||||
can also override the default base (10) in the second
|
||||
parameter, which handles input with prefixes such as
|
||||
0b, 0o and 0x for bases 2, 8 and 16 respectively.
|
||||
"""
|
||||
try:
|
||||
return int(value)
|
||||
return int(value, base)
|
||||
except (TypeError, ValueError):
|
||||
# this quirk is necessary so that "42.23"|int gives 42.
|
||||
try:
|
||||
@@ -612,7 +623,6 @@ def do_batch(value, linecount, fill_with=None):
|
||||
{%- endfor %}
|
||||
</table>
|
||||
"""
|
||||
result = []
|
||||
tmp = []
|
||||
for item in value:
|
||||
if len(tmp) == linecount:
|
||||
@@ -753,7 +763,7 @@ def do_mark_unsafe(value):
|
||||
|
||||
|
||||
def do_reverse(value):
|
||||
"""Reverse the object or return an iterator the iterates over it the other
|
||||
"""Reverse the object or return an iterator that iterates over it the other
|
||||
way round.
|
||||
"""
|
||||
if isinstance(value, string_types):
|
||||
@@ -772,7 +782,7 @@ def do_reverse(value):
|
||||
@environmentfilter
|
||||
def do_attr(environment, obj, name):
|
||||
"""Get an attribute of an object. ``foo|attr("bar")`` works like
|
||||
``foo["bar"]`` just that always an attribute is returned and items are not
|
||||
``foo.bar`` just that always an attribute is returned and items are not
|
||||
looked up.
|
||||
|
||||
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
|
||||
@@ -842,14 +852,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 applying 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 +869,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 applying a test to the object and
|
||||
rejecting the ones with the test succeeding.
|
||||
|
||||
Example usage:
|
||||
|
||||
@@ -874,8 +885,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 applying a test to an attribute of an
|
||||
object and only selecting the ones with the test succeeding.
|
||||
|
||||
Example usage:
|
||||
|
||||
@@ -891,8 +902,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 applying a test to an attribute of an
|
||||
object or the attribute and rejecting the ones with the test succeeding.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
@@ -933,55 +944,53 @@ def _select_or_reject(args, kwargs, modfunc, lookup_attr):
|
||||
|
||||
|
||||
FILTERS = {
|
||||
'abs': abs,
|
||||
'attr': do_attr,
|
||||
'replace': do_replace,
|
||||
'upper': do_upper,
|
||||
'lower': do_lower,
|
||||
'escape': escape,
|
||||
'e': escape,
|
||||
'forceescape': do_forceescape,
|
||||
'batch': do_batch,
|
||||
'capitalize': do_capitalize,
|
||||
'title': do_title,
|
||||
'default': do_default,
|
||||
'd': do_default,
|
||||
'join': do_join,
|
||||
'count': len,
|
||||
'dictsort': do_dictsort,
|
||||
'sort': do_sort,
|
||||
'length': len,
|
||||
'reverse': do_reverse,
|
||||
'center': do_center,
|
||||
'indent': do_indent,
|
||||
'title': do_title,
|
||||
'capitalize': do_capitalize,
|
||||
'count': len,
|
||||
'd': do_default,
|
||||
'default': do_default,
|
||||
'dictsort': do_dictsort,
|
||||
'e': escape,
|
||||
'escape': escape,
|
||||
'filesizeformat': do_filesizeformat,
|
||||
'first': do_first,
|
||||
'float': do_float,
|
||||
'forceescape': do_forceescape,
|
||||
'format': do_format,
|
||||
'groupby': do_groupby,
|
||||
'indent': do_indent,
|
||||
'int': do_int,
|
||||
'join': do_join,
|
||||
'last': do_last,
|
||||
'length': len,
|
||||
'list': do_list,
|
||||
'lower': do_lower,
|
||||
'map': do_map,
|
||||
'pprint': do_pprint,
|
||||
'random': do_random,
|
||||
'reject': do_reject,
|
||||
'rejectattr': do_rejectattr,
|
||||
'filesizeformat': do_filesizeformat,
|
||||
'pprint': do_pprint,
|
||||
'truncate': do_truncate,
|
||||
'wordwrap': do_wordwrap,
|
||||
'wordcount': do_wordcount,
|
||||
'int': do_int,
|
||||
'float': do_float,
|
||||
'string': soft_unicode,
|
||||
'list': do_list,
|
||||
'urlize': do_urlize,
|
||||
'format': do_format,
|
||||
'trim': do_trim,
|
||||
'striptags': do_striptags,
|
||||
'replace': do_replace,
|
||||
'reverse': do_reverse,
|
||||
'round': do_round,
|
||||
'safe': do_mark_safe,
|
||||
'select': do_select,
|
||||
'selectattr': do_selectattr,
|
||||
'slice': do_slice,
|
||||
'batch': do_batch,
|
||||
'sort': do_sort,
|
||||
'string': soft_unicode,
|
||||
'striptags': do_striptags,
|
||||
'sum': do_sum,
|
||||
'abs': abs,
|
||||
'round': do_round,
|
||||
'groupby': do_groupby,
|
||||
'safe': do_mark_safe,
|
||||
'title': do_title,
|
||||
'trim': do_trim,
|
||||
'truncate': do_truncate,
|
||||
'upper': do_upper,
|
||||
'urlencode': do_urlencode,
|
||||
'urlize': do_urlize,
|
||||
'wordcount': do_wordcount,
|
||||
'wordwrap': do_wordwrap,
|
||||
'xmlattr': do_xmlattr,
|
||||
'urlencode': do_urlencode
|
||||
}
|
||||
|
||||
Vendored
+9
-8
@@ -20,8 +20,8 @@ from operator import itemgetter
|
||||
from collections import deque
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
from jinja2.utils import LRUCache
|
||||
from jinja2._compat import next, iteritems, implements_iterator, text_type, \
|
||||
intern
|
||||
from jinja2._compat import iteritems, implements_iterator, text_type, \
|
||||
intern, PY2
|
||||
|
||||
|
||||
# cache for the lexers. Exists in order to be able to have multiple
|
||||
@@ -136,8 +136,8 @@ operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
|
||||
|
||||
ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
|
||||
TOKEN_COMMENT_END, TOKEN_WHITESPACE,
|
||||
TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
|
||||
TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
|
||||
TOKEN_LINECOMMENT_BEGIN, TOKEN_LINECOMMENT_END,
|
||||
TOKEN_LINECOMMENT])
|
||||
ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
|
||||
TOKEN_COMMENT, TOKEN_LINECOMMENT])
|
||||
|
||||
@@ -578,10 +578,11 @@ class Lexer(object):
|
||||
# we do that for support of semi broken APIs
|
||||
# as datetime.datetime.strftime. On python 3 this
|
||||
# call becomes a noop thanks to 2to3
|
||||
try:
|
||||
value = str(value)
|
||||
except UnicodeError:
|
||||
pass
|
||||
if PY2:
|
||||
try:
|
||||
value = value.encode('ascii')
|
||||
except UnicodeError:
|
||||
pass
|
||||
elif token == 'integer':
|
||||
value = int(value)
|
||||
elif token == 'float':
|
||||
|
||||
Vendored
+15
-5
@@ -141,20 +141,28 @@ class FileSystemLoader(BaseLoader):
|
||||
|
||||
The loader takes the path to the templates as string, or if multiple
|
||||
locations are wanted a list of them which is then looked up in the
|
||||
given order:
|
||||
given order::
|
||||
|
||||
>>> loader = FileSystemLoader('/path/to/templates')
|
||||
>>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
|
||||
|
||||
Per default the template encoding is ``'utf-8'`` which can be changed
|
||||
by setting the `encoding` parameter to something else.
|
||||
|
||||
To follow symbolic links, set the *followlinks* parameter to ``True``::
|
||||
|
||||
>>> loader = FileSystemLoader('/path/to/templates', followlinks=True)
|
||||
|
||||
.. versionchanged:: 2.8+
|
||||
The *followlinks* parameter was added.
|
||||
"""
|
||||
|
||||
def __init__(self, searchpath, encoding='utf-8'):
|
||||
def __init__(self, searchpath, encoding='utf-8', followlinks=False):
|
||||
if isinstance(searchpath, string_types):
|
||||
searchpath = [searchpath]
|
||||
self.searchpath = list(searchpath)
|
||||
self.encoding = encoding
|
||||
self.followlinks = followlinks
|
||||
|
||||
def get_source(self, environment, template):
|
||||
pieces = split_template_path(template)
|
||||
@@ -169,6 +177,7 @@ class FileSystemLoader(BaseLoader):
|
||||
f.close()
|
||||
|
||||
mtime = path.getmtime(filename)
|
||||
|
||||
def uptodate():
|
||||
try:
|
||||
return path.getmtime(filename) == mtime
|
||||
@@ -180,7 +189,8 @@ class FileSystemLoader(BaseLoader):
|
||||
def list_templates(self):
|
||||
found = set()
|
||||
for searchpath in self.searchpath:
|
||||
for dirpath, dirnames, filenames in os.walk(searchpath):
|
||||
walk_dir = os.walk(searchpath, followlinks=self.followlinks)
|
||||
for dirpath, dirnames, filenames in walk_dir:
|
||||
for filename in filenames:
|
||||
template = os.path.join(dirpath, filename) \
|
||||
[len(searchpath):].strip(os.path.sep) \
|
||||
@@ -281,7 +291,7 @@ class DictLoader(BaseLoader):
|
||||
|
||||
class FunctionLoader(BaseLoader):
|
||||
"""A loader that is passed a function which does the loading. The
|
||||
function becomes the name of the template passed and has to return either
|
||||
function receives the name of the template and has to return either
|
||||
an unicode string with the template source, a tuple in the form ``(source,
|
||||
filename, uptodatefunc)`` or `None` if the template does not exist.
|
||||
|
||||
@@ -349,7 +359,7 @@ class PrefixLoader(BaseLoader):
|
||||
def load(self, environment, name, globals=None):
|
||||
loader, local_name = self.get_loader(name)
|
||||
try:
|
||||
return loader.load(environment, local_name)
|
||||
return loader.load(environment, local_name, globals)
|
||||
except TemplateNotFound:
|
||||
# re-raise the exception with the correct fileame here.
|
||||
# (the one that includes the prefix)
|
||||
|
||||
Vendored
+2
-2
@@ -39,8 +39,8 @@ def find_undeclared_variables(ast):
|
||||
>>> from jinja2 import Environment, meta
|
||||
>>> env = Environment()
|
||||
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
||||
>>> meta.find_undeclared_variables(ast)
|
||||
set(['bar'])
|
||||
>>> meta.find_undeclared_variables(ast) == set(['bar'])
|
||||
True
|
||||
|
||||
.. admonition:: Implementation
|
||||
|
||||
|
||||
Vendored
+12
-4
@@ -12,16 +12,16 @@
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import types
|
||||
import operator
|
||||
|
||||
from collections import deque
|
||||
from jinja2.utils import Markup
|
||||
from jinja2._compat import next, izip, with_metaclass, text_type, \
|
||||
method_type, function_type
|
||||
from jinja2._compat import izip, with_metaclass, text_type
|
||||
|
||||
|
||||
#: the types we support for context functions
|
||||
_context_function_types = (function_type, method_type)
|
||||
_context_function_types = (types.FunctionType, types.MethodType)
|
||||
|
||||
|
||||
_binop_to_func = {
|
||||
@@ -232,6 +232,9 @@ class Node(with_metaclass(NodeType, object)):
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
# Restore Python 2 hashing behavior on Python 3
|
||||
__hash__ = object.__hash__
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (
|
||||
self.__class__.__name__,
|
||||
@@ -344,6 +347,11 @@ class Assign(Stmt):
|
||||
fields = ('target', 'node')
|
||||
|
||||
|
||||
class AssignBlock(Stmt):
|
||||
"""Assigns a block to a target."""
|
||||
fields = ('target', 'body')
|
||||
|
||||
|
||||
class Expr(Node):
|
||||
"""Baseclass for all expressions."""
|
||||
abstract = True
|
||||
@@ -743,7 +751,7 @@ class Add(BinExpr):
|
||||
|
||||
|
||||
class Sub(BinExpr):
|
||||
"""Substract the right from the left node."""
|
||||
"""Subtract the right from the left node."""
|
||||
operator = '-'
|
||||
|
||||
|
||||
|
||||
Vendored
+17
-13
@@ -11,10 +11,9 @@
|
||||
from jinja2 import nodes
|
||||
from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
|
||||
from jinja2.lexer import describe_token, describe_token_expr
|
||||
from jinja2._compat import next, imap
|
||||
from jinja2._compat import imap
|
||||
|
||||
|
||||
#: statements that callinto
|
||||
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
|
||||
'macro', 'include', 'from', 'import',
|
||||
'set'])
|
||||
@@ -169,9 +168,12 @@ class Parser(object):
|
||||
"""Parse an assign statement."""
|
||||
lineno = next(self.stream).lineno
|
||||
target = self.parse_assign_target()
|
||||
self.stream.expect('assign')
|
||||
expr = self.parse_tuple()
|
||||
return nodes.Assign(target, expr, lineno=lineno)
|
||||
if self.stream.skip_if('assign'):
|
||||
expr = self.parse_tuple()
|
||||
return nodes.Assign(target, expr, lineno=lineno)
|
||||
body = self.parse_statements(('name:endset',),
|
||||
drop_needle=True)
|
||||
return nodes.AssignBlock(target, body, lineno=lineno)
|
||||
|
||||
def parse_for(self):
|
||||
"""Parse a for loop."""
|
||||
@@ -312,6 +314,8 @@ class Parser(object):
|
||||
arg.set_ctx('param')
|
||||
if self.stream.skip_if('assign'):
|
||||
defaults.append(self.parse_expression())
|
||||
elif defaults:
|
||||
self.fail('non-default argument follows default argument')
|
||||
args.append(arg)
|
||||
self.stream.expect('rparen')
|
||||
|
||||
@@ -434,8 +438,8 @@ class Parser(object):
|
||||
ops.append(nodes.Operand(token_type, self.parse_add()))
|
||||
elif self.stream.skip_if('name:in'):
|
||||
ops.append(nodes.Operand('in', self.parse_add()))
|
||||
elif self.stream.current.test('name:not') and \
|
||||
self.stream.look().test('name:in'):
|
||||
elif (self.stream.current.test('name:not') and
|
||||
self.stream.look().test('name:in')):
|
||||
self.stream.skip(2)
|
||||
ops.append(nodes.Operand('notin', self.parse_add()))
|
||||
else:
|
||||
@@ -771,7 +775,7 @@ class Parser(object):
|
||||
else:
|
||||
ensure(dyn_args is None and dyn_kwargs is None)
|
||||
if self.stream.current.type == 'name' and \
|
||||
self.stream.look().type == 'assign':
|
||||
self.stream.look().type == 'assign':
|
||||
key = self.stream.current.value
|
||||
self.stream.skip(2)
|
||||
value = self.parse_expression()
|
||||
@@ -824,11 +828,11 @@ class Parser(object):
|
||||
kwargs = []
|
||||
if self.stream.current.type == 'lparen':
|
||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
|
||||
elif self.stream.current.type in ('name', 'string', 'integer',
|
||||
'float', 'lparen', 'lbracket',
|
||||
'lbrace') and not \
|
||||
self.stream.current.test_any('name:else', 'name:or',
|
||||
'name:and'):
|
||||
elif (self.stream.current.type in ('name', 'string', 'integer',
|
||||
'float', 'lparen', 'lbracket',
|
||||
'lbrace') and not
|
||||
self.stream.current.test_any('name:else', 'name:or',
|
||||
'name:and')):
|
||||
if self.stream.current.test('name:is'):
|
||||
self.fail('You cannot chain multiple tests with is')
|
||||
args = [self.parse_expression()]
|
||||
|
||||
Vendored
+114
-18
@@ -8,13 +8,15 @@
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from itertools import chain
|
||||
from jinja2.nodes import EvalContext, _context_function_types
|
||||
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
|
||||
internalcode, object_type_repr
|
||||
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
||||
TemplateNotFound
|
||||
from jinja2._compat import next, imap, text_type, iteritems, \
|
||||
from jinja2._compat import imap, text_type, iteritems, \
|
||||
implements_iterator, implements_to_string, string_types, PY2
|
||||
|
||||
|
||||
@@ -22,7 +24,7 @@ from jinja2._compat import next, imap, text_type, iteritems, \
|
||||
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
|
||||
'TemplateRuntimeError', 'missing', 'concat', 'escape',
|
||||
'markup_join', 'unicode_join', 'to_string', 'identity',
|
||||
'TemplateNotFound']
|
||||
'TemplateNotFound', 'make_logging_undefined']
|
||||
|
||||
#: the name of the function that is used to convert something into
|
||||
#: a string. We can just use the text type here.
|
||||
@@ -67,7 +69,8 @@ def new_context(environment, template_name, blocks, vars=None,
|
||||
for key, value in iteritems(locals):
|
||||
if key[:2] == 'l_' and value is not missing:
|
||||
parent[key[2:]] = value
|
||||
return Context(environment, parent, template_name, blocks)
|
||||
return environment.context_class(environment, parent, template_name,
|
||||
blocks)
|
||||
|
||||
|
||||
class TemplateReference(object):
|
||||
@@ -171,7 +174,7 @@ class Context(object):
|
||||
:func:`environmentfunction`.
|
||||
"""
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
__traceback_hide__ = True # noqa
|
||||
|
||||
# Allow callable classes to take a context
|
||||
fn = __obj.__call__
|
||||
@@ -339,10 +342,11 @@ class LoopContext(object):
|
||||
# if was not possible to get the length of the iterator when
|
||||
# the loop context was created (ie: iterating over a generator)
|
||||
# we have to convert the iterable into a sequence and use the
|
||||
# length of that.
|
||||
# length of that + the number of iterations so far.
|
||||
iterable = tuple(self._iterator)
|
||||
self._iterator = iter(iterable)
|
||||
self._length = len(iterable) + self.index0 + 1
|
||||
iterations_done = self.index0 + 2
|
||||
self._length = len(iterable) + iterations_done
|
||||
return self._length
|
||||
|
||||
def __repr__(self):
|
||||
@@ -441,7 +445,7 @@ class Macro(object):
|
||||
@implements_to_string
|
||||
class Undefined(object):
|
||||
"""The default undefined type. This undefined type can be printed and
|
||||
iterated over, but every other access will raise an :exc:`UndefinedError`:
|
||||
iterated over, but every other access will raise an :exc:`jinja2.exceptions.UndefinedError`:
|
||||
|
||||
>>> foo = Undefined(name='foo')
|
||||
>>> str(foo)
|
||||
@@ -451,7 +455,7 @@ class Undefined(object):
|
||||
>>> foo + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: 'foo' is undefined
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
"""
|
||||
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
|
||||
'_undefined_exception')
|
||||
@@ -465,7 +469,7 @@ class Undefined(object):
|
||||
@internalcode
|
||||
def _fail_with_undefined_error(self, *args, **kwargs):
|
||||
"""Regular callback function for undefined objects that raises an
|
||||
`UndefinedError` on call.
|
||||
`jinja2.exceptions.UndefinedError` on call.
|
||||
"""
|
||||
if self._undefined_hint is None:
|
||||
if self._undefined_obj is missing:
|
||||
@@ -491,12 +495,21 @@ class Undefined(object):
|
||||
return self._fail_with_undefined_error()
|
||||
|
||||
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
|
||||
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
|
||||
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
|
||||
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
|
||||
__float__ = __complex__ = __pow__ = __rpow__ = \
|
||||
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
|
||||
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
|
||||
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
|
||||
__float__ = __complex__ = __pow__ = __rpow__ = \
|
||||
_fail_with_undefined_error
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return id(type(self))
|
||||
|
||||
def __str__(self):
|
||||
return u''
|
||||
|
||||
@@ -509,11 +522,93 @@ class Undefined(object):
|
||||
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
__bool__ = __nonzero__
|
||||
|
||||
def __repr__(self):
|
||||
return 'Undefined'
|
||||
|
||||
|
||||
def make_logging_undefined(logger=None, base=None):
|
||||
"""Given a logger object this returns a new undefined class that will
|
||||
log certain failures. It will log iterations and printing. If no
|
||||
logger is given a default logger is created.
|
||||
|
||||
Example::
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
LoggingUndefined = make_logging_undefined(
|
||||
logger=logger,
|
||||
base=Undefined
|
||||
)
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
:param logger: the logger to use. If not provided, a default logger
|
||||
is created.
|
||||
:param base: the base class to add logging functionality to. This
|
||||
defaults to :class:`Undefined`.
|
||||
"""
|
||||
if logger is None:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(logging.StreamHandler(sys.stderr))
|
||||
if base is None:
|
||||
base = Undefined
|
||||
|
||||
def _log_message(undef):
|
||||
if undef._undefined_hint is None:
|
||||
if undef._undefined_obj is missing:
|
||||
hint = '%s is undefined' % undef._undefined_name
|
||||
elif not isinstance(undef._undefined_name, string_types):
|
||||
hint = '%s has no element %s' % (
|
||||
object_type_repr(undef._undefined_obj),
|
||||
undef._undefined_name)
|
||||
else:
|
||||
hint = '%s has no attribute %s' % (
|
||||
object_type_repr(undef._undefined_obj),
|
||||
undef._undefined_name)
|
||||
else:
|
||||
hint = undef._undefined_hint
|
||||
logger.warning('Template variable warning: %s', hint)
|
||||
|
||||
class LoggingUndefined(base):
|
||||
|
||||
def _fail_with_undefined_error(self, *args, **kwargs):
|
||||
try:
|
||||
return base._fail_with_undefined_error(self, *args, **kwargs)
|
||||
except self._undefined_exception as e:
|
||||
logger.error('Template variable error: %s', str(e))
|
||||
raise e
|
||||
|
||||
def __str__(self):
|
||||
rv = base.__str__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
def __iter__(self):
|
||||
rv = base.__iter__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
if PY2:
|
||||
def __nonzero__(self):
|
||||
rv = base.__nonzero__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
def __unicode__(self):
|
||||
rv = base.__unicode__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
else:
|
||||
def __bool__(self):
|
||||
rv = base.__bool__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
return LoggingUndefined
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class DebugUndefined(Undefined):
|
||||
"""An undefined that returns the debug info when printed.
|
||||
@@ -526,7 +621,7 @@ class DebugUndefined(Undefined):
|
||||
>>> foo + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: 'foo' is undefined
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
@@ -551,19 +646,20 @@ class StrictUndefined(Undefined):
|
||||
>>> str(foo)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: 'foo' is undefined
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
>>> not foo
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: 'foo' is undefined
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
>>> foo + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UndefinedError: 'foo' is undefined
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
"""
|
||||
__slots__ = ()
|
||||
__iter__ = __str__ = __len__ = __nonzero__ = __eq__ = \
|
||||
__ne__ = __bool__ = Undefined._fail_with_undefined_error
|
||||
__ne__ = __bool__ = __hash__ = \
|
||||
Undefined._fail_with_undefined_error
|
||||
|
||||
|
||||
# remove remaining slots attributes, after the metaclass did the magic they
|
||||
|
||||
Vendored
+15
-16
@@ -12,19 +12,25 @@
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
import types
|
||||
import operator
|
||||
from jinja2.environment import Environment
|
||||
from jinja2.exceptions import SecurityError
|
||||
from jinja2._compat import string_types, function_type, method_type, \
|
||||
traceback_type, code_type, frame_type, generator_type, PY2
|
||||
from jinja2._compat import string_types, PY2
|
||||
|
||||
|
||||
#: maximum number of items a range may produce
|
||||
MAX_RANGE = 100000
|
||||
|
||||
#: attributes of function objects that are considered unsafe.
|
||||
UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
|
||||
'func_defaults', 'func_globals'])
|
||||
if PY2:
|
||||
UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
|
||||
'func_defaults', 'func_globals'])
|
||||
else:
|
||||
# On versions > python 2 the special attributes on functions are gone,
|
||||
# but they remain on methods and generators for whatever reason.
|
||||
UNSAFE_FUNCTION_ATTRIBUTES = set()
|
||||
|
||||
|
||||
#: unsafe method attributes. function attributes are unsafe for methods too
|
||||
UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
|
||||
@@ -32,11 +38,6 @@ UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
|
||||
#: unsafe generator attirbutes.
|
||||
UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
|
||||
|
||||
# On versions > python 2 the special attributes on functions are gone,
|
||||
# but they remain on methods and generators for whatever reason.
|
||||
if not PY2:
|
||||
UNSAFE_FUNCTION_ATTRIBUTES = set()
|
||||
|
||||
import warnings
|
||||
|
||||
# make sure we don't warn in python 2.6 about stuff we don't care about
|
||||
@@ -124,26 +125,24 @@ def is_internal_attribute(obj, attr):
|
||||
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
|
||||
|
||||
>>> from jinja2.sandbox import is_internal_attribute
|
||||
>>> is_internal_attribute(lambda: None, "func_code")
|
||||
True
|
||||
>>> is_internal_attribute((lambda x:x).func_code, 'co_code')
|
||||
>>> is_internal_attribute(str, "mro")
|
||||
True
|
||||
>>> is_internal_attribute(str, "upper")
|
||||
False
|
||||
"""
|
||||
if isinstance(obj, function_type):
|
||||
if isinstance(obj, types.FunctionType):
|
||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
||||
return True
|
||||
elif isinstance(obj, method_type):
|
||||
elif isinstance(obj, types.MethodType):
|
||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
|
||||
attr in UNSAFE_METHOD_ATTRIBUTES:
|
||||
return True
|
||||
elif isinstance(obj, type):
|
||||
if attr == 'mro':
|
||||
return True
|
||||
elif isinstance(obj, (code_type, traceback_type, frame_type)):
|
||||
elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
|
||||
return True
|
||||
elif isinstance(obj, generator_type):
|
||||
elif isinstance(obj, types.GeneratorType):
|
||||
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
|
||||
return True
|
||||
return attr.startswith('__')
|
||||
|
||||
Vendored
+28
-4
@@ -9,9 +9,10 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
from collections import Mapping
|
||||
from jinja2.runtime import Undefined
|
||||
from jinja2._compat import text_type, string_types, mapping_types
|
||||
|
||||
from jinja2._compat import text_type, string_types, integer_types
|
||||
import decimal
|
||||
|
||||
number_re = re.compile(r'^-?\d+(\.\d+)?$')
|
||||
regex_type = type(number_re)
|
||||
@@ -82,12 +83,12 @@ def test_mapping(value):
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return isinstance(value, mapping_types)
|
||||
return isinstance(value, Mapping)
|
||||
|
||||
|
||||
def test_number(value):
|
||||
"""Return true if the variable is a number."""
|
||||
return isinstance(value, (int, float, complex))
|
||||
return isinstance(value, integer_types + (float, complex, decimal.Decimal))
|
||||
|
||||
|
||||
def test_sequence(value):
|
||||
@@ -102,6 +103,28 @@ def test_sequence(value):
|
||||
return True
|
||||
|
||||
|
||||
def test_equalto(value, other):
|
||||
"""Check if an object has the same value as another object:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% if foo.expression is equalto 42 %}
|
||||
the foo attribute evaluates to the constant 42
|
||||
{% endif %}
|
||||
|
||||
This appears to be a useless test as it does exactly the same as the
|
||||
``==`` operator, but it can be useful when used together with the
|
||||
`selectattr` function:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ users|selectattr("email", "equalto", "foo@bar.invalid") }}
|
||||
|
||||
.. versionadded:: 2.8
|
||||
"""
|
||||
return value == other
|
||||
|
||||
|
||||
def test_sameas(value, other):
|
||||
"""Check if an object points to the same memory address than another
|
||||
object:
|
||||
@@ -145,5 +168,6 @@ TESTS = {
|
||||
'iterable': test_iterable,
|
||||
'callable': test_callable,
|
||||
'sameas': test_sameas,
|
||||
'equalto': test_equalto,
|
||||
'escaped': test_escaped
|
||||
}
|
||||
|
||||
Vendored
-155
@@ -1,155 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
All the unittests of Jinja2. These tests can be executed by
|
||||
either running run-tests.py using multiple Python versions at
|
||||
the same time.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
from traceback import format_exception
|
||||
from jinja2 import loaders
|
||||
from jinja2._compat import PY2
|
||||
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
dict_loader = loaders.DictLoader({
|
||||
'justdict.html': 'FOO'
|
||||
})
|
||||
package_loader = loaders.PackageLoader('jinja2.testsuite.res', 'templates')
|
||||
filesystem_loader = loaders.FileSystemLoader(here + '/res/templates')
|
||||
function_loader = loaders.FunctionLoader({'justfunction.html': 'FOO'}.get)
|
||||
choice_loader = loaders.ChoiceLoader([dict_loader, package_loader])
|
||||
prefix_loader = loaders.PrefixLoader({
|
||||
'a': filesystem_loader,
|
||||
'b': dict_loader
|
||||
})
|
||||
|
||||
|
||||
class JinjaTestCase(unittest.TestCase):
|
||||
|
||||
### use only these methods for testing. If you need standard
|
||||
### unittest method, wrap them!
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
self.setup()
|
||||
|
||||
def tearDown(self):
|
||||
self.teardown()
|
||||
|
||||
def assert_equal(self, a, b):
|
||||
return self.assertEqual(a, b)
|
||||
|
||||
def assert_raises(self, *args, **kwargs):
|
||||
return self.assertRaises(*args, **kwargs)
|
||||
|
||||
def assert_traceback_matches(self, callback, expected_tb):
|
||||
try:
|
||||
callback()
|
||||
except Exception as e:
|
||||
tb = format_exception(*sys.exc_info())
|
||||
if re.search(expected_tb.strip(), ''.join(tb)) is None:
|
||||
raise self.fail('Traceback did not match:\n\n%s\nexpected:\n%s'
|
||||
% (''.join(tb), expected_tb))
|
||||
else:
|
||||
self.fail('Expected exception')
|
||||
|
||||
|
||||
def find_all_tests(suite):
|
||||
"""Yields all the tests and their names from a given suite."""
|
||||
suites = [suite]
|
||||
while suites:
|
||||
s = suites.pop()
|
||||
try:
|
||||
suites.extend(s)
|
||||
except TypeError:
|
||||
yield s, '%s.%s.%s' % (
|
||||
s.__class__.__module__,
|
||||
s.__class__.__name__,
|
||||
s._testMethodName
|
||||
)
|
||||
|
||||
|
||||
class BetterLoader(unittest.TestLoader):
|
||||
"""A nicer loader that solves two problems. First of all we are setting
|
||||
up tests from different sources and we're doing this programmatically
|
||||
which breaks the default loading logic so this is required anyways.
|
||||
Secondly this loader has a nicer interpolation for test names than the
|
||||
default one so you can just do ``run-tests.py ViewTestCase`` and it
|
||||
will work.
|
||||
"""
|
||||
|
||||
def getRootSuite(self):
|
||||
return suite()
|
||||
|
||||
def loadTestsFromName(self, name, module=None):
|
||||
root = self.getRootSuite()
|
||||
if name == 'suite':
|
||||
return root
|
||||
|
||||
all_tests = []
|
||||
for testcase, testname in find_all_tests(root):
|
||||
if testname == name or \
|
||||
testname.endswith('.' + name) or \
|
||||
('.' + name + '.') in testname or \
|
||||
testname.startswith(name + '.'):
|
||||
all_tests.append(testcase)
|
||||
|
||||
if not all_tests:
|
||||
raise LookupError('could not find test case for "%s"' % name)
|
||||
|
||||
if len(all_tests) == 1:
|
||||
return all_tests[0]
|
||||
rv = unittest.TestSuite()
|
||||
for test in all_tests:
|
||||
rv.addTest(test)
|
||||
return rv
|
||||
|
||||
|
||||
def suite():
|
||||
from jinja2.testsuite import ext, filters, tests, core_tags, \
|
||||
loader, inheritance, imports, lexnparse, security, api, \
|
||||
regression, debug, utils, doctests
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(ext.suite())
|
||||
suite.addTest(filters.suite())
|
||||
suite.addTest(tests.suite())
|
||||
suite.addTest(core_tags.suite())
|
||||
suite.addTest(loader.suite())
|
||||
suite.addTest(inheritance.suite())
|
||||
suite.addTest(imports.suite())
|
||||
suite.addTest(lexnparse.suite())
|
||||
suite.addTest(security.suite())
|
||||
suite.addTest(api.suite())
|
||||
suite.addTest(regression.suite())
|
||||
suite.addTest(debug.suite())
|
||||
suite.addTest(utils.suite())
|
||||
|
||||
# doctests will not run on python 3 currently. Too many issues
|
||||
# with that, do not test that on that platform.
|
||||
if PY2:
|
||||
suite.addTest(doctests.suite())
|
||||
|
||||
return suite
|
||||
|
||||
|
||||
def main():
|
||||
"""Runs the testsuite as command line application."""
|
||||
try:
|
||||
unittest.main(testLoader=BetterLoader(), defaultTest='suite')
|
||||
except Exception as e:
|
||||
print('Error: %s' % e)
|
||||
Vendored
-261
@@ -1,261 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.api
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests the public API and related stuff.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
from jinja2._compat import next
|
||||
|
||||
from jinja2 import Environment, Undefined, DebugUndefined, \
|
||||
StrictUndefined, UndefinedError, meta, \
|
||||
is_undefined, Template, DictLoader
|
||||
from jinja2.utils import Cycler
|
||||
|
||||
env = Environment()
|
||||
|
||||
|
||||
class ExtendedAPITestCase(JinjaTestCase):
|
||||
|
||||
def test_item_and_attribute(self):
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
for env in Environment(), SandboxedEnvironment():
|
||||
# the |list is necessary for python3
|
||||
tmpl = env.from_string('{{ foo.items()|list }}')
|
||||
assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
|
||||
tmpl = env.from_string('{{ foo|attr("items")()|list }}')
|
||||
assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
|
||||
tmpl = env.from_string('{{ foo["items"] }}')
|
||||
assert tmpl.render(foo={'items': 42}) == '42'
|
||||
|
||||
def test_finalizer(self):
|
||||
def finalize_none_empty(value):
|
||||
if value is None:
|
||||
value = u''
|
||||
return value
|
||||
env = Environment(finalize=finalize_none_empty)
|
||||
tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}')
|
||||
assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo'
|
||||
tmpl = env.from_string('<{{ none }}>')
|
||||
assert tmpl.render() == '<>'
|
||||
|
||||
def test_cycler(self):
|
||||
items = 1, 2, 3
|
||||
c = Cycler(*items)
|
||||
for item in items + items:
|
||||
assert c.current == item
|
||||
assert next(c) == item
|
||||
next(c)
|
||||
assert c.current == 2
|
||||
c.reset()
|
||||
assert c.current == 1
|
||||
|
||||
def test_expressions(self):
|
||||
expr = env.compile_expression("foo")
|
||||
assert expr() is None
|
||||
assert expr(foo=42) == 42
|
||||
expr2 = env.compile_expression("foo", undefined_to_none=False)
|
||||
assert is_undefined(expr2())
|
||||
|
||||
expr = env.compile_expression("42 + foo")
|
||||
assert expr(foo=42) == 84
|
||||
|
||||
def test_template_passthrough(self):
|
||||
t = Template('Content')
|
||||
assert env.get_template(t) is t
|
||||
assert env.select_template([t]) is t
|
||||
assert env.get_or_select_template([t]) is t
|
||||
assert env.get_or_select_template(t) is t
|
||||
|
||||
def test_autoescape_autoselect(self):
|
||||
def select_autoescape(name):
|
||||
if name is None or '.' not in name:
|
||||
return False
|
||||
return name.endswith('.html')
|
||||
env = Environment(autoescape=select_autoescape,
|
||||
loader=DictLoader({
|
||||
'test.txt': '{{ foo }}',
|
||||
'test.html': '{{ foo }}'
|
||||
}))
|
||||
t = env.get_template('test.txt')
|
||||
assert t.render(foo='<foo>') == '<foo>'
|
||||
t = env.get_template('test.html')
|
||||
assert t.render(foo='<foo>') == '<foo>'
|
||||
t = env.from_string('{{ foo }}')
|
||||
assert t.render(foo='<foo>') == '<foo>'
|
||||
|
||||
|
||||
class MetaTestCase(JinjaTestCase):
|
||||
|
||||
def test_find_undeclared_variables(self):
|
||||
ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
||||
x = meta.find_undeclared_variables(ast)
|
||||
assert x == set(['bar'])
|
||||
|
||||
ast = env.parse('{% set foo = 42 %}{{ bar + foo }}'
|
||||
'{% macro meh(x) %}{{ x }}{% endmacro %}'
|
||||
'{% for item in seq %}{{ muh(item) + meh(seq) }}{% endfor %}')
|
||||
x = meta.find_undeclared_variables(ast)
|
||||
assert x == set(['bar', 'seq', 'muh'])
|
||||
|
||||
def test_find_refererenced_templates(self):
|
||||
ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert next(i) == 'layout.html'
|
||||
assert next(i) is None
|
||||
assert list(i) == []
|
||||
|
||||
ast = env.parse('{% extends "layout.html" %}'
|
||||
'{% from "test.html" import a, b as c %}'
|
||||
'{% import "meh.html" as meh %}'
|
||||
'{% include "muh.html" %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ['layout.html', 'test.html', 'meh.html', 'muh.html']
|
||||
|
||||
def test_find_included_templates(self):
|
||||
ast = env.parse('{% include ["foo.html", "bar.html"] %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ['foo.html', 'bar.html']
|
||||
|
||||
ast = env.parse('{% include ("foo.html", "bar.html") %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ['foo.html', 'bar.html']
|
||||
|
||||
ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ['foo.html', 'bar.html', None]
|
||||
|
||||
ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ['foo.html', 'bar.html', None]
|
||||
|
||||
|
||||
class StreamingTestCase(JinjaTestCase):
|
||||
|
||||
def test_basic_streaming(self):
|
||||
tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
|
||||
"}} - {{ item }}</li>{%- endfor %}</ul>")
|
||||
stream = tmpl.stream(seq=list(range(4)))
|
||||
self.assert_equal(next(stream), '<ul>')
|
||||
self.assert_equal(next(stream), '<li>1 - 0</li>')
|
||||
self.assert_equal(next(stream), '<li>2 - 1</li>')
|
||||
self.assert_equal(next(stream), '<li>3 - 2</li>')
|
||||
self.assert_equal(next(stream), '<li>4 - 3</li>')
|
||||
self.assert_equal(next(stream), '</ul>')
|
||||
|
||||
def test_buffered_streaming(self):
|
||||
tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
|
||||
"}} - {{ item }}</li>{%- endfor %}</ul>")
|
||||
stream = tmpl.stream(seq=list(range(4)))
|
||||
stream.enable_buffering(size=3)
|
||||
self.assert_equal(next(stream), u'<ul><li>1 - 0</li><li>2 - 1</li>')
|
||||
self.assert_equal(next(stream), u'<li>3 - 2</li><li>4 - 3</li></ul>')
|
||||
|
||||
def test_streaming_behavior(self):
|
||||
tmpl = env.from_string("")
|
||||
stream = tmpl.stream()
|
||||
assert not stream.buffered
|
||||
stream.enable_buffering(20)
|
||||
assert stream.buffered
|
||||
stream.disable_buffering()
|
||||
assert not stream.buffered
|
||||
|
||||
def test_dump_stream(self):
|
||||
tmp = tempfile.mkdtemp()
|
||||
try:
|
||||
tmpl = env.from_string(u"\u2713")
|
||||
stream = tmpl.stream()
|
||||
stream.dump(os.path.join(tmp, 'dump.txt'), 'utf-8')
|
||||
with open(os.path.join(tmp, 'dump.txt'), 'rb') as f:
|
||||
self.assertEqual(f.read(), b'\xe2\x9c\x93')
|
||||
finally:
|
||||
shutil.rmtree(tmp)
|
||||
|
||||
|
||||
class UndefinedTestCase(JinjaTestCase):
|
||||
|
||||
def test_stopiteration_is_undefined(self):
|
||||
def test():
|
||||
raise StopIteration()
|
||||
t = Template('A{{ test() }}B')
|
||||
assert t.render(test=test) == 'AB'
|
||||
t = Template('A{{ test().missingattribute }}B')
|
||||
self.assert_raises(UndefinedError, t.render, test=test)
|
||||
|
||||
def test_undefined_and_special_attributes(self):
|
||||
try:
|
||||
Undefined('Foo').__dict__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
assert False, "Expected actual attribute error"
|
||||
|
||||
def test_default_undefined(self):
|
||||
env = Environment(undefined=Undefined)
|
||||
self.assert_equal(env.from_string('{{ missing }}').render(), u'')
|
||||
self.assert_raises(UndefinedError,
|
||||
env.from_string('{{ missing.attribute }}').render)
|
||||
self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
|
||||
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
|
||||
self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42), '')
|
||||
self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
|
||||
|
||||
def test_debug_undefined(self):
|
||||
env = Environment(undefined=DebugUndefined)
|
||||
self.assert_equal(env.from_string('{{ missing }}').render(), '{{ missing }}')
|
||||
self.assert_raises(UndefinedError,
|
||||
env.from_string('{{ missing.attribute }}').render)
|
||||
self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
|
||||
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
|
||||
self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42),
|
||||
u"{{ no such element: int object['missing'] }}")
|
||||
self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
|
||||
|
||||
def test_strict_undefined(self):
|
||||
env = Environment(undefined=StrictUndefined)
|
||||
self.assert_raises(UndefinedError, env.from_string('{{ missing }}').render)
|
||||
self.assert_raises(UndefinedError, env.from_string('{{ missing.attribute }}').render)
|
||||
self.assert_raises(UndefinedError, env.from_string('{{ missing|list }}').render)
|
||||
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
|
||||
self.assert_raises(UndefinedError, env.from_string('{{ foo.missing }}').render, foo=42)
|
||||
self.assert_raises(UndefinedError, env.from_string('{{ not missing }}').render)
|
||||
self.assert_equal(env.from_string('{{ missing|default("default", true) }}').render(), 'default')
|
||||
|
||||
def test_indexing_gives_undefined(self):
|
||||
t = Template("{{ var[42].foo }}")
|
||||
self.assert_raises(UndefinedError, t.render, var=0)
|
||||
|
||||
def test_none_gives_proper_error(self):
|
||||
try:
|
||||
Environment().getattr(None, 'split')()
|
||||
except UndefinedError as e:
|
||||
assert e.message == "'None' has no attribute 'split'"
|
||||
else:
|
||||
assert False, 'expected exception'
|
||||
|
||||
def test_object_repr(self):
|
||||
try:
|
||||
Undefined(obj=42, name='upper')()
|
||||
except UndefinedError as e:
|
||||
assert e.message == "'int object' has no attribute 'upper'"
|
||||
else:
|
||||
assert False, 'expected exception'
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(ExtendedAPITestCase))
|
||||
suite.addTest(unittest.makeSuite(MetaTestCase))
|
||||
suite.addTest(unittest.makeSuite(StreamingTestCase))
|
||||
suite.addTest(unittest.makeSuite(UndefinedTestCase))
|
||||
return suite
|
||||
Vendored
-305
@@ -1,305 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.core_tags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the core tags like for and if.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Environment, TemplateSyntaxError, UndefinedError, \
|
||||
DictLoader
|
||||
|
||||
env = Environment()
|
||||
|
||||
|
||||
class ForLoopTestCase(JinjaTestCase):
|
||||
|
||||
def test_simple(self):
|
||||
tmpl = env.from_string('{% for item in seq %}{{ item }}{% endfor %}')
|
||||
assert tmpl.render(seq=list(range(10))) == '0123456789'
|
||||
|
||||
def test_else(self):
|
||||
tmpl = env.from_string('{% for item in seq %}XXX{% else %}...{% endfor %}')
|
||||
assert tmpl.render() == '...'
|
||||
|
||||
def test_empty_blocks(self):
|
||||
tmpl = env.from_string('<{% for item in seq %}{% else %}{% endfor %}>')
|
||||
assert tmpl.render() == '<>'
|
||||
|
||||
def test_context_vars(self):
|
||||
tmpl = env.from_string('''{% for item in seq -%}
|
||||
{{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
|
||||
loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
|
||||
loop.length }}###{% endfor %}''')
|
||||
one, two, _ = tmpl.render(seq=[0, 1]).split('###')
|
||||
(one_index, one_index0, one_revindex, one_revindex0, one_first,
|
||||
one_last, one_length) = one.split('|')
|
||||
(two_index, two_index0, two_revindex, two_revindex0, two_first,
|
||||
two_last, two_length) = two.split('|')
|
||||
|
||||
assert int(one_index) == 1 and int(two_index) == 2
|
||||
assert int(one_index0) == 0 and int(two_index0) == 1
|
||||
assert int(one_revindex) == 2 and int(two_revindex) == 1
|
||||
assert int(one_revindex0) == 1 and int(two_revindex0) == 0
|
||||
assert one_first == 'True' and two_first == 'False'
|
||||
assert one_last == 'False' and two_last == 'True'
|
||||
assert one_length == two_length == '2'
|
||||
|
||||
def test_cycling(self):
|
||||
tmpl = env.from_string('''{% for item in seq %}{{
|
||||
loop.cycle('<1>', '<2>') }}{% endfor %}{%
|
||||
for item in seq %}{{ loop.cycle(*through) }}{% endfor %}''')
|
||||
output = tmpl.render(seq=list(range(4)), through=('<1>', '<2>'))
|
||||
assert output == '<1><2>' * 4
|
||||
|
||||
def test_scope(self):
|
||||
tmpl = env.from_string('{% for item in seq %}{% endfor %}{{ item }}')
|
||||
output = tmpl.render(seq=list(range(10)))
|
||||
assert not output
|
||||
|
||||
def test_varlen(self):
|
||||
def inner():
|
||||
for item in range(5):
|
||||
yield item
|
||||
tmpl = env.from_string('{% for item in iter %}{{ item }}{% endfor %}')
|
||||
output = tmpl.render(iter=inner())
|
||||
assert output == '01234'
|
||||
|
||||
def test_noniter(self):
|
||||
tmpl = env.from_string('{% for item in none %}...{% endfor %}')
|
||||
self.assert_raises(TypeError, tmpl.render)
|
||||
|
||||
def test_recursive(self):
|
||||
tmpl = env.from_string('''{% for item in seq recursive -%}
|
||||
[{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
|
||||
{%- endfor %}''')
|
||||
assert tmpl.render(seq=[
|
||||
dict(a=1, b=[dict(a=1), dict(a=2)]),
|
||||
dict(a=2, b=[dict(a=1), dict(a=2)]),
|
||||
dict(a=3, b=[dict(a='a')])
|
||||
]) == '[1<[1][2]>][2<[1][2]>][3<[a]>]'
|
||||
|
||||
def test_recursive_depth0(self):
|
||||
tmpl = env.from_string('''{% for item in seq recursive -%}
|
||||
[{{ loop.depth0 }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
|
||||
{%- endfor %}''')
|
||||
self.assertEqual(tmpl.render(seq=[
|
||||
dict(a=1, b=[dict(a=1), dict(a=2)]),
|
||||
dict(a=2, b=[dict(a=1), dict(a=2)]),
|
||||
dict(a=3, b=[dict(a='a')])
|
||||
]), '[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]')
|
||||
|
||||
def test_recursive_depth(self):
|
||||
tmpl = env.from_string('''{% for item in seq recursive -%}
|
||||
[{{ loop.depth }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
|
||||
{%- endfor %}''')
|
||||
self.assertEqual(tmpl.render(seq=[
|
||||
dict(a=1, b=[dict(a=1), dict(a=2)]),
|
||||
dict(a=2, b=[dict(a=1), dict(a=2)]),
|
||||
dict(a=3, b=[dict(a='a')])
|
||||
]), '[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]')
|
||||
|
||||
def test_looploop(self):
|
||||
tmpl = env.from_string('''{% for row in table %}
|
||||
{%- set rowloop = loop -%}
|
||||
{% for cell in row -%}
|
||||
[{{ rowloop.index }}|{{ loop.index }}]
|
||||
{%- endfor %}
|
||||
{%- endfor %}''')
|
||||
assert tmpl.render(table=['ab', 'cd']) == '[1|1][1|2][2|1][2|2]'
|
||||
|
||||
def test_reversed_bug(self):
|
||||
tmpl = env.from_string('{% for i in items %}{{ i }}'
|
||||
'{% if not loop.last %}'
|
||||
',{% endif %}{% endfor %}')
|
||||
assert tmpl.render(items=reversed([3, 2, 1])) == '1,2,3'
|
||||
|
||||
def test_loop_errors(self):
|
||||
tmpl = env.from_string('''{% for item in [1] if loop.index
|
||||
== 0 %}...{% endfor %}''')
|
||||
self.assert_raises(UndefinedError, tmpl.render)
|
||||
tmpl = env.from_string('''{% for item in [] %}...{% else
|
||||
%}{{ loop }}{% endfor %}''')
|
||||
assert tmpl.render() == ''
|
||||
|
||||
def test_loop_filter(self):
|
||||
tmpl = env.from_string('{% for item in range(10) if item '
|
||||
'is even %}[{{ item }}]{% endfor %}')
|
||||
assert tmpl.render() == '[0][2][4][6][8]'
|
||||
tmpl = env.from_string('''
|
||||
{%- for item in range(10) if item is even %}[{{
|
||||
loop.index }}:{{ item }}]{% endfor %}''')
|
||||
assert tmpl.render() == '[1:0][2:2][3:4][4:6][5:8]'
|
||||
|
||||
def test_loop_unassignable(self):
|
||||
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||
'{% for loop in seq %}...{% endfor %}')
|
||||
|
||||
def test_scoped_special_var(self):
|
||||
t = env.from_string('{% for s in seq %}[{{ loop.first }}{% for c in s %}'
|
||||
'|{{ loop.first }}{% endfor %}]{% endfor %}')
|
||||
assert t.render(seq=('ab', 'cd')) == '[True|True|False][False|True|False]'
|
||||
|
||||
def test_scoped_loop_var(self):
|
||||
t = env.from_string('{% for x in seq %}{{ loop.first }}'
|
||||
'{% for y in seq %}{% endfor %}{% endfor %}')
|
||||
assert t.render(seq='ab') == 'TrueFalse'
|
||||
t = env.from_string('{% for x in seq %}{% for y in seq %}'
|
||||
'{{ loop.first }}{% endfor %}{% endfor %}')
|
||||
assert t.render(seq='ab') == 'TrueFalseTrueFalse'
|
||||
|
||||
def test_recursive_empty_loop_iter(self):
|
||||
t = env.from_string('''
|
||||
{%- for item in foo recursive -%}{%- endfor -%}
|
||||
''')
|
||||
assert t.render(dict(foo=[])) == ''
|
||||
|
||||
def test_call_in_loop(self):
|
||||
t = env.from_string('''
|
||||
{%- macro do_something() -%}
|
||||
[{{ caller() }}]
|
||||
{%- endmacro %}
|
||||
|
||||
{%- for i in [1, 2, 3] %}
|
||||
{%- call do_something() -%}
|
||||
{{ i }}
|
||||
{%- endcall %}
|
||||
{%- endfor -%}
|
||||
''')
|
||||
assert t.render() == '[1][2][3]'
|
||||
|
||||
def test_scoping_bug(self):
|
||||
t = env.from_string('''
|
||||
{%- for item in foo %}...{{ item }}...{% endfor %}
|
||||
{%- macro item(a) %}...{{ a }}...{% endmacro %}
|
||||
{{- item(2) -}}
|
||||
''')
|
||||
assert t.render(foo=(1,)) == '...1......2...'
|
||||
|
||||
def test_unpacking(self):
|
||||
tmpl = env.from_string('{% for a, b, c in [[1, 2, 3]] %}'
|
||||
'{{ a }}|{{ b }}|{{ c }}{% endfor %}')
|
||||
assert tmpl.render() == '1|2|3'
|
||||
|
||||
|
||||
class IfConditionTestCase(JinjaTestCase):
|
||||
|
||||
def test_simple(self):
|
||||
tmpl = env.from_string('''{% if true %}...{% endif %}''')
|
||||
assert tmpl.render() == '...'
|
||||
|
||||
def test_elif(self):
|
||||
tmpl = env.from_string('''{% if false %}XXX{% elif true
|
||||
%}...{% else %}XXX{% endif %}''')
|
||||
assert tmpl.render() == '...'
|
||||
|
||||
def test_else(self):
|
||||
tmpl = env.from_string('{% if false %}XXX{% else %}...{% endif %}')
|
||||
assert tmpl.render() == '...'
|
||||
|
||||
def test_empty(self):
|
||||
tmpl = env.from_string('[{% if true %}{% else %}{% endif %}]')
|
||||
assert tmpl.render() == '[]'
|
||||
|
||||
def test_complete(self):
|
||||
tmpl = env.from_string('{% if a %}A{% elif b %}B{% elif c == d %}'
|
||||
'C{% else %}D{% endif %}')
|
||||
assert tmpl.render(a=0, b=False, c=42, d=42.0) == 'C'
|
||||
|
||||
def test_no_scope(self):
|
||||
tmpl = env.from_string('{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}')
|
||||
assert tmpl.render(a=True) == '1'
|
||||
tmpl = env.from_string('{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}')
|
||||
assert tmpl.render() == '1'
|
||||
|
||||
|
||||
class MacrosTestCase(JinjaTestCase):
|
||||
env = Environment(trim_blocks=True)
|
||||
|
||||
def test_simple(self):
|
||||
tmpl = self.env.from_string('''\
|
||||
{% macro say_hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||
{{ say_hello('Peter') }}''')
|
||||
assert tmpl.render() == 'Hello Peter!'
|
||||
|
||||
def test_scoping(self):
|
||||
tmpl = self.env.from_string('''\
|
||||
{% macro level1(data1) %}
|
||||
{% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %}
|
||||
{{ level2('bar') }}{% endmacro %}
|
||||
{{ level1('foo') }}''')
|
||||
assert tmpl.render() == 'foo|bar'
|
||||
|
||||
def test_arguments(self):
|
||||
tmpl = self.env.from_string('''\
|
||||
{% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %}
|
||||
{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}''')
|
||||
assert tmpl.render() == '||c|d|a||c|d|a|b|c|d|1|2|3|d'
|
||||
|
||||
def test_varargs(self):
|
||||
tmpl = self.env.from_string('''\
|
||||
{% macro test() %}{{ varargs|join('|') }}{% endmacro %}\
|
||||
{{ test(1, 2, 3) }}''')
|
||||
assert tmpl.render() == '1|2|3'
|
||||
|
||||
def test_simple_call(self):
|
||||
tmpl = self.env.from_string('''\
|
||||
{% macro test() %}[[{{ caller() }}]]{% endmacro %}\
|
||||
{% call test() %}data{% endcall %}''')
|
||||
assert tmpl.render() == '[[data]]'
|
||||
|
||||
def test_complex_call(self):
|
||||
tmpl = self.env.from_string('''\
|
||||
{% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\
|
||||
{% call(data) test() %}{{ data }}{% endcall %}''')
|
||||
assert tmpl.render() == '[[data]]'
|
||||
|
||||
def test_caller_undefined(self):
|
||||
tmpl = self.env.from_string('''\
|
||||
{% set caller = 42 %}\
|
||||
{% macro test() %}{{ caller is not defined }}{% endmacro %}\
|
||||
{{ test() }}''')
|
||||
assert tmpl.render() == 'True'
|
||||
|
||||
def test_include(self):
|
||||
self.env = Environment(loader=DictLoader({'include':
|
||||
'{% macro test(foo) %}[{{ foo }}]{% endmacro %}'}))
|
||||
tmpl = self.env.from_string('{% from "include" import test %}{{ test("foo") }}')
|
||||
assert tmpl.render() == '[foo]'
|
||||
|
||||
def test_macro_api(self):
|
||||
tmpl = self.env.from_string('{% macro foo(a, b) %}{% endmacro %}'
|
||||
'{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}'
|
||||
'{% macro baz() %}{{ caller() }}{% endmacro %}')
|
||||
assert tmpl.module.foo.arguments == ('a', 'b')
|
||||
assert tmpl.module.foo.defaults == ()
|
||||
assert tmpl.module.foo.name == 'foo'
|
||||
assert not tmpl.module.foo.caller
|
||||
assert not tmpl.module.foo.catch_kwargs
|
||||
assert not tmpl.module.foo.catch_varargs
|
||||
assert tmpl.module.bar.arguments == ()
|
||||
assert tmpl.module.bar.defaults == ()
|
||||
assert not tmpl.module.bar.caller
|
||||
assert tmpl.module.bar.catch_kwargs
|
||||
assert tmpl.module.bar.catch_varargs
|
||||
assert tmpl.module.baz.caller
|
||||
|
||||
def test_callself(self):
|
||||
tmpl = self.env.from_string('{% macro foo(x) %}{{ x }}{% if x > 1 %}|'
|
||||
'{{ foo(x - 1) }}{% endif %}{% endmacro %}'
|
||||
'{{ foo(5) }}')
|
||||
assert tmpl.render() == '5|4|3|2|1'
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(ForLoopTestCase))
|
||||
suite.addTest(unittest.makeSuite(IfConditionTestCase))
|
||||
suite.addTest(unittest.makeSuite(MacrosTestCase))
|
||||
return suite
|
||||
Vendored
-58
@@ -1,58 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.debug
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests the debug system.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase, filesystem_loader
|
||||
|
||||
from jinja2 import Environment, TemplateSyntaxError
|
||||
|
||||
env = Environment(loader=filesystem_loader)
|
||||
|
||||
|
||||
class DebugTestCase(JinjaTestCase):
|
||||
|
||||
def test_runtime_error(self):
|
||||
def test():
|
||||
tmpl.render(fail=lambda: 1 / 0)
|
||||
tmpl = env.get_template('broken.html')
|
||||
self.assert_traceback_matches(test, r'''
|
||||
File ".*?broken.html", line 2, in (top-level template code|<module>)
|
||||
\{\{ fail\(\) \}\}
|
||||
File ".*?debug.pyc?", line \d+, in <lambda>
|
||||
tmpl\.render\(fail=lambda: 1 / 0\)
|
||||
ZeroDivisionError: (int(eger)? )?division (or modulo )?by zero
|
||||
''')
|
||||
|
||||
def test_syntax_error(self):
|
||||
# XXX: the .*? is necessary for python3 which does not hide
|
||||
# some of the stack frames we don't want to show. Not sure
|
||||
# what's up with that, but that is not that critical. Should
|
||||
# be fixed though.
|
||||
self.assert_traceback_matches(lambda: env.get_template('syntaxerror.html'), r'''(?sm)
|
||||
File ".*?syntaxerror.html", line 4, in (template|<module>)
|
||||
\{% endif %\}.*?
|
||||
(jinja2\.exceptions\.)?TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
|
||||
''')
|
||||
|
||||
def test_regular_syntax_error(self):
|
||||
def test():
|
||||
raise TemplateSyntaxError('wtf', 42)
|
||||
self.assert_traceback_matches(test, r'''
|
||||
File ".*debug.pyc?", line \d+, in test
|
||||
raise TemplateSyntaxError\('wtf', 42\)
|
||||
(jinja2\.exceptions\.)?TemplateSyntaxError: wtf
|
||||
line 42''')
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(DebugTestCase))
|
||||
return suite
|
||||
Vendored
-29
@@ -1,29 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.doctests
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The doctests. Collects all tests we want to test from
|
||||
the Jinja modules.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
import doctest
|
||||
|
||||
|
||||
def suite():
|
||||
from jinja2 import utils, sandbox, runtime, meta, loaders, \
|
||||
ext, environment, bccache, nodes
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(doctest.DocTestSuite(utils))
|
||||
suite.addTest(doctest.DocTestSuite(sandbox))
|
||||
suite.addTest(doctest.DocTestSuite(runtime))
|
||||
suite.addTest(doctest.DocTestSuite(meta))
|
||||
suite.addTest(doctest.DocTestSuite(loaders))
|
||||
suite.addTest(doctest.DocTestSuite(ext))
|
||||
suite.addTest(doctest.DocTestSuite(environment))
|
||||
suite.addTest(doctest.DocTestSuite(bccache))
|
||||
suite.addTest(doctest.DocTestSuite(nodes))
|
||||
return suite
|
||||
Vendored
-459
@@ -1,459 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.ext
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests for the extensions.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Environment, DictLoader, contextfunction, nodes
|
||||
from jinja2.exceptions import TemplateAssertionError
|
||||
from jinja2.ext import Extension
|
||||
from jinja2.lexer import Token, count_newlines
|
||||
from jinja2._compat import next, BytesIO, itervalues, text_type
|
||||
|
||||
importable_object = 23
|
||||
|
||||
_gettext_re = re.compile(r'_\((.*?)\)(?s)')
|
||||
|
||||
|
||||
i18n_templates = {
|
||||
'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
|
||||
'{% block body %}{% endblock %}',
|
||||
'child.html': '{% extends "master.html" %}{% block body %}'
|
||||
'{% trans %}watch out{% endtrans %}{% endblock %}',
|
||||
'plural.html': '{% trans user_count %}One user online{% pluralize %}'
|
||||
'{{ user_count }} users online{% endtrans %}',
|
||||
'plural2.html': '{% trans user_count=get_user_count() %}{{ user_count }}s'
|
||||
'{% pluralize %}{{ user_count }}p{% endtrans %}',
|
||||
'stringformat.html': '{{ _("User: %(num)s")|format(num=user_count) }}'
|
||||
}
|
||||
|
||||
newstyle_i18n_templates = {
|
||||
'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
|
||||
'{% block body %}{% endblock %}',
|
||||
'child.html': '{% extends "master.html" %}{% block body %}'
|
||||
'{% trans %}watch out{% endtrans %}{% endblock %}',
|
||||
'plural.html': '{% trans user_count %}One user online{% pluralize %}'
|
||||
'{{ user_count }} users online{% endtrans %}',
|
||||
'stringformat.html': '{{ _("User: %(num)s", num=user_count) }}',
|
||||
'ngettext.html': '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
|
||||
'ngettext_long.html': '{% trans num=apples %}{{ num }} apple{% pluralize %}'
|
||||
'{{ num }} apples{% endtrans %}',
|
||||
'transvars1.html': '{% trans %}User: {{ num }}{% endtrans %}',
|
||||
'transvars2.html': '{% trans num=count %}User: {{ num }}{% endtrans %}',
|
||||
'transvars3.html': '{% trans count=num %}User: {{ count }}{% endtrans %}',
|
||||
'novars.html': '{% trans %}%(hello)s{% endtrans %}',
|
||||
'vars.html': '{% trans %}{{ foo }}%(foo)s{% endtrans %}',
|
||||
'explicitvars.html': '{% trans foo="42" %}%(foo)s{% endtrans %}'
|
||||
}
|
||||
|
||||
|
||||
languages = {
|
||||
'de': {
|
||||
'missing': u'fehlend',
|
||||
'watch out': u'pass auf',
|
||||
'One user online': u'Ein Benutzer online',
|
||||
'%(user_count)s users online': u'%(user_count)s Benutzer online',
|
||||
'User: %(num)s': u'Benutzer: %(num)s',
|
||||
'User: %(count)s': u'Benutzer: %(count)s',
|
||||
'%(num)s apple': u'%(num)s Apfel',
|
||||
'%(num)s apples': u'%(num)s Äpfel'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@contextfunction
|
||||
def gettext(context, string):
|
||||
language = context.get('LANGUAGE', 'en')
|
||||
return languages.get(language, {}).get(string, string)
|
||||
|
||||
|
||||
@contextfunction
|
||||
def ngettext(context, s, p, n):
|
||||
language = context.get('LANGUAGE', 'en')
|
||||
if n != 1:
|
||||
return languages.get(language, {}).get(p, p)
|
||||
return languages.get(language, {}).get(s, s)
|
||||
|
||||
|
||||
i18n_env = Environment(
|
||||
loader=DictLoader(i18n_templates),
|
||||
extensions=['jinja2.ext.i18n']
|
||||
)
|
||||
i18n_env.globals.update({
|
||||
'_': gettext,
|
||||
'gettext': gettext,
|
||||
'ngettext': ngettext
|
||||
})
|
||||
|
||||
newstyle_i18n_env = Environment(
|
||||
loader=DictLoader(newstyle_i18n_templates),
|
||||
extensions=['jinja2.ext.i18n']
|
||||
)
|
||||
newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
|
||||
|
||||
class TestExtension(Extension):
|
||||
tags = set(['test'])
|
||||
ext_attr = 42
|
||||
|
||||
def parse(self, parser):
|
||||
return nodes.Output([self.call_method('_dump', [
|
||||
nodes.EnvironmentAttribute('sandboxed'),
|
||||
self.attr('ext_attr'),
|
||||
nodes.ImportedName(__name__ + '.importable_object'),
|
||||
nodes.ContextReference()
|
||||
])]).set_lineno(next(parser.stream).lineno)
|
||||
|
||||
def _dump(self, sandboxed, ext_attr, imported_object, context):
|
||||
return '%s|%s|%s|%s' % (
|
||||
sandboxed,
|
||||
ext_attr,
|
||||
imported_object,
|
||||
context.blocks
|
||||
)
|
||||
|
||||
|
||||
class PreprocessorExtension(Extension):
|
||||
|
||||
def preprocess(self, source, name, filename=None):
|
||||
return source.replace('[[TEST]]', '({{ foo }})')
|
||||
|
||||
|
||||
class StreamFilterExtension(Extension):
|
||||
|
||||
def filter_stream(self, stream):
|
||||
for token in stream:
|
||||
if token.type == 'data':
|
||||
for t in self.interpolate(token):
|
||||
yield t
|
||||
else:
|
||||
yield token
|
||||
|
||||
def interpolate(self, token):
|
||||
pos = 0
|
||||
end = len(token.value)
|
||||
lineno = token.lineno
|
||||
while 1:
|
||||
match = _gettext_re.search(token.value, pos)
|
||||
if match is None:
|
||||
break
|
||||
value = token.value[pos:match.start()]
|
||||
if value:
|
||||
yield Token(lineno, 'data', value)
|
||||
lineno += count_newlines(token.value)
|
||||
yield Token(lineno, 'variable_begin', None)
|
||||
yield Token(lineno, 'name', 'gettext')
|
||||
yield Token(lineno, 'lparen', None)
|
||||
yield Token(lineno, 'string', match.group(1))
|
||||
yield Token(lineno, 'rparen', None)
|
||||
yield Token(lineno, 'variable_end', None)
|
||||
pos = match.end()
|
||||
if pos < end:
|
||||
yield Token(lineno, 'data', token.value[pos:])
|
||||
|
||||
|
||||
class ExtensionsTestCase(JinjaTestCase):
|
||||
|
||||
def test_extend_late(self):
|
||||
env = Environment()
|
||||
env.add_extension('jinja2.ext.autoescape')
|
||||
t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
|
||||
assert t.render() == '<test>'
|
||||
|
||||
def test_loop_controls(self):
|
||||
env = Environment(extensions=['jinja2.ext.loopcontrols'])
|
||||
|
||||
tmpl = env.from_string('''
|
||||
{%- for item in [1, 2, 3, 4] %}
|
||||
{%- if item % 2 == 0 %}{% continue %}{% endif -%}
|
||||
{{ item }}
|
||||
{%- endfor %}''')
|
||||
assert tmpl.render() == '13'
|
||||
|
||||
tmpl = env.from_string('''
|
||||
{%- for item in [1, 2, 3, 4] %}
|
||||
{%- if item > 2 %}{% break %}{% endif -%}
|
||||
{{ item }}
|
||||
{%- endfor %}''')
|
||||
assert tmpl.render() == '12'
|
||||
|
||||
def test_do(self):
|
||||
env = Environment(extensions=['jinja2.ext.do'])
|
||||
tmpl = env.from_string('''
|
||||
{%- set items = [] %}
|
||||
{%- for char in "foo" %}
|
||||
{%- do items.append(loop.index0 ~ char) %}
|
||||
{%- endfor %}{{ items|join(', ') }}''')
|
||||
assert tmpl.render() == '0f, 1o, 2o'
|
||||
|
||||
def test_with(self):
|
||||
env = Environment(extensions=['jinja2.ext.with_'])
|
||||
tmpl = env.from_string('''\
|
||||
{% with a=42, b=23 -%}
|
||||
{{ a }} = {{ b }}
|
||||
{% endwith -%}
|
||||
{{ a }} = {{ b }}\
|
||||
''')
|
||||
assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
|
||||
== ['42 = 23', '1 = 2']
|
||||
|
||||
def test_extension_nodes(self):
|
||||
env = Environment(extensions=[TestExtension])
|
||||
tmpl = env.from_string('{% test %}')
|
||||
assert tmpl.render() == 'False|42|23|{}'
|
||||
|
||||
def test_identifier(self):
|
||||
assert TestExtension.identifier == __name__ + '.TestExtension'
|
||||
|
||||
def test_rebinding(self):
|
||||
original = Environment(extensions=[TestExtension])
|
||||
overlay = original.overlay()
|
||||
for env in original, overlay:
|
||||
for ext in itervalues(env.extensions):
|
||||
assert ext.environment is env
|
||||
|
||||
def test_preprocessor_extension(self):
|
||||
env = Environment(extensions=[PreprocessorExtension])
|
||||
tmpl = env.from_string('{[[TEST]]}')
|
||||
assert tmpl.render(foo=42) == '{(42)}'
|
||||
|
||||
def test_streamfilter_extension(self):
|
||||
env = Environment(extensions=[StreamFilterExtension])
|
||||
env.globals['gettext'] = lambda x: x.upper()
|
||||
tmpl = env.from_string('Foo _(bar) Baz')
|
||||
out = tmpl.render()
|
||||
assert out == 'Foo BAR Baz'
|
||||
|
||||
def test_extension_ordering(self):
|
||||
class T1(Extension):
|
||||
priority = 1
|
||||
class T2(Extension):
|
||||
priority = 2
|
||||
env = Environment(extensions=[T1, T2])
|
||||
ext = list(env.iter_extensions())
|
||||
assert ext[0].__class__ is T1
|
||||
assert ext[1].__class__ is T2
|
||||
|
||||
|
||||
class InternationalizationTestCase(JinjaTestCase):
|
||||
|
||||
def test_trans(self):
|
||||
tmpl = i18n_env.get_template('child.html')
|
||||
assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
|
||||
|
||||
def test_trans_plural(self):
|
||||
tmpl = i18n_env.get_template('plural.html')
|
||||
assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
|
||||
assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
|
||||
|
||||
def test_trans_plural_with_functions(self):
|
||||
tmpl = i18n_env.get_template('plural2.html')
|
||||
def get_user_count():
|
||||
get_user_count.called += 1
|
||||
return 1
|
||||
get_user_count.called = 0
|
||||
assert tmpl.render(LANGUAGE='de', get_user_count=get_user_count) == '1s'
|
||||
assert get_user_count.called == 1
|
||||
|
||||
def test_complex_plural(self):
|
||||
tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
|
||||
'pluralize count %}{{ count }} items{% endtrans %}')
|
||||
assert tmpl.render() == '2 items'
|
||||
self.assert_raises(TemplateAssertionError, i18n_env.from_string,
|
||||
'{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
|
||||
|
||||
def test_trans_stringformatting(self):
|
||||
tmpl = i18n_env.get_template('stringformat.html')
|
||||
assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
|
||||
|
||||
def test_extract(self):
|
||||
from jinja2.ext import babel_extract
|
||||
source = BytesIO('''
|
||||
{{ gettext('Hello World') }}
|
||||
{% trans %}Hello World{% endtrans %}
|
||||
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
|
||||
'''.encode('ascii')) # make python 3 happy
|
||||
assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
|
||||
(2, 'gettext', u'Hello World', []),
|
||||
(3, 'gettext', u'Hello World', []),
|
||||
(4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
|
||||
]
|
||||
|
||||
def test_comment_extract(self):
|
||||
from jinja2.ext import babel_extract
|
||||
source = BytesIO('''
|
||||
{# trans first #}
|
||||
{{ gettext('Hello World') }}
|
||||
{% trans %}Hello World{% endtrans %}{# trans second #}
|
||||
{#: third #}
|
||||
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
|
||||
'''.encode('utf-8')) # make python 3 happy
|
||||
assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
|
||||
(3, 'gettext', u'Hello World', ['first']),
|
||||
(4, 'gettext', u'Hello World', ['second']),
|
||||
(6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
|
||||
]
|
||||
|
||||
|
||||
class NewstyleInternationalizationTestCase(JinjaTestCase):
|
||||
|
||||
def test_trans(self):
|
||||
tmpl = newstyle_i18n_env.get_template('child.html')
|
||||
assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
|
||||
|
||||
def test_trans_plural(self):
|
||||
tmpl = newstyle_i18n_env.get_template('plural.html')
|
||||
assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
|
||||
assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
|
||||
|
||||
def test_complex_plural(self):
|
||||
tmpl = newstyle_i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
|
||||
'pluralize count %}{{ count }} items{% endtrans %}')
|
||||
assert tmpl.render() == '2 items'
|
||||
self.assert_raises(TemplateAssertionError, i18n_env.from_string,
|
||||
'{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
|
||||
|
||||
def test_trans_stringformatting(self):
|
||||
tmpl = newstyle_i18n_env.get_template('stringformat.html')
|
||||
assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
|
||||
|
||||
def test_newstyle_plural(self):
|
||||
tmpl = newstyle_i18n_env.get_template('ngettext.html')
|
||||
assert tmpl.render(LANGUAGE='de', apples=1) == '1 Apfel'
|
||||
assert tmpl.render(LANGUAGE='de', apples=5) == u'5 Äpfel'
|
||||
|
||||
def test_autoescape_support(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape',
|
||||
'jinja2.ext.i18n'])
|
||||
env.install_gettext_callables(lambda x: u'<strong>Wert: %(name)s</strong>',
|
||||
lambda s, p, n: s, newstyle=True)
|
||||
t = env.from_string('{% autoescape ae %}{{ gettext("foo", name='
|
||||
'"<test>") }}{% endautoescape %}')
|
||||
assert t.render(ae=True) == '<strong>Wert: <test></strong>'
|
||||
assert t.render(ae=False) == '<strong>Wert: <test></strong>'
|
||||
|
||||
def test_num_used_twice(self):
|
||||
tmpl = newstyle_i18n_env.get_template('ngettext_long.html')
|
||||
assert tmpl.render(apples=5, LANGUAGE='de') == u'5 Äpfel'
|
||||
|
||||
def test_num_called_num(self):
|
||||
source = newstyle_i18n_env.compile('''
|
||||
{% trans num=3 %}{{ num }} apple{% pluralize
|
||||
%}{{ num }} apples{% endtrans %}
|
||||
''', raw=True)
|
||||
# quite hacky, but the only way to properly test that. The idea is
|
||||
# that the generated code does not pass num twice (although that
|
||||
# would work) for better performance. This only works on the
|
||||
# newstyle gettext of course
|
||||
assert re.search(r"l_ngettext, u?'\%\(num\)s apple', u?'\%\(num\)s "
|
||||
r"apples', 3", source) is not None
|
||||
|
||||
def test_trans_vars(self):
|
||||
t1 = newstyle_i18n_env.get_template('transvars1.html')
|
||||
t2 = newstyle_i18n_env.get_template('transvars2.html')
|
||||
t3 = newstyle_i18n_env.get_template('transvars3.html')
|
||||
assert t1.render(num=1, LANGUAGE='de') == 'Benutzer: 1'
|
||||
assert t2.render(count=23, LANGUAGE='de') == 'Benutzer: 23'
|
||||
assert t3.render(num=42, LANGUAGE='de') == 'Benutzer: 42'
|
||||
|
||||
def test_novars_vars_escaping(self):
|
||||
t = newstyle_i18n_env.get_template('novars.html')
|
||||
assert t.render() == '%(hello)s'
|
||||
t = newstyle_i18n_env.get_template('vars.html')
|
||||
assert t.render(foo='42') == '42%(foo)s'
|
||||
t = newstyle_i18n_env.get_template('explicitvars.html')
|
||||
assert t.render() == '%(foo)s'
|
||||
|
||||
|
||||
class AutoEscapeTestCase(JinjaTestCase):
|
||||
|
||||
def test_scoped_setting(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=True)
|
||||
tmpl = env.from_string('''
|
||||
{{ "<HelloWorld>" }}
|
||||
{% autoescape false %}
|
||||
{{ "<HelloWorld>" }}
|
||||
{% endautoescape %}
|
||||
{{ "<HelloWorld>" }}
|
||||
''')
|
||||
assert tmpl.render().split() == \
|
||||
[u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
|
||||
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=False)
|
||||
tmpl = env.from_string('''
|
||||
{{ "<HelloWorld>" }}
|
||||
{% autoescape true %}
|
||||
{{ "<HelloWorld>" }}
|
||||
{% endautoescape %}
|
||||
{{ "<HelloWorld>" }}
|
||||
''')
|
||||
assert tmpl.render().split() == \
|
||||
[u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
|
||||
|
||||
def test_nonvolatile(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=True)
|
||||
tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
|
||||
assert tmpl.render() == ' foo="<test>"'
|
||||
tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
|
||||
'|xmlattr|escape }}{% endautoescape %}')
|
||||
assert tmpl.render() == ' foo="&lt;test&gt;"'
|
||||
|
||||
def test_volatile(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=True)
|
||||
tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
|
||||
'|xmlattr|escape }}{% endautoescape %}')
|
||||
assert tmpl.render(foo=False) == ' foo="&lt;test&gt;"'
|
||||
assert tmpl.render(foo=True) == ' foo="<test>"'
|
||||
|
||||
def test_scoping(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'])
|
||||
tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
|
||||
'{% endautoescape %}{{ x }}{{ "<y>" }}')
|
||||
assert tmpl.render(x=1) == '<x>1<y>'
|
||||
|
||||
def test_volatile_scoping(self):
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'])
|
||||
tmplsource = '''
|
||||
{% autoescape val %}
|
||||
{% macro foo(x) %}
|
||||
[{{ x }}]
|
||||
{% endmacro %}
|
||||
{{ foo().__class__.__name__ }}
|
||||
{% endautoescape %}
|
||||
{{ '<testing>' }}
|
||||
'''
|
||||
tmpl = env.from_string(tmplsource)
|
||||
assert tmpl.render(val=True).split()[0] == 'Markup'
|
||||
assert tmpl.render(val=False).split()[0] == text_type.__name__
|
||||
|
||||
# looking at the source we should see <testing> there in raw
|
||||
# (and then escaped as well)
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'])
|
||||
pysource = env.compile(tmplsource, raw=True)
|
||||
assert '<testing>\\n' in pysource
|
||||
|
||||
env = Environment(extensions=['jinja2.ext.autoescape'],
|
||||
autoescape=True)
|
||||
pysource = env.compile(tmplsource, raw=True)
|
||||
assert '<testing>\\n' in pysource
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(ExtensionsTestCase))
|
||||
suite.addTest(unittest.makeSuite(InternationalizationTestCase))
|
||||
suite.addTest(unittest.makeSuite(NewstyleInternationalizationTestCase))
|
||||
suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
|
||||
return suite
|
||||
Vendored
-509
@@ -1,509 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.filters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests for the jinja filters.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Markup, Environment
|
||||
from jinja2._compat import text_type, implements_to_string
|
||||
|
||||
env = Environment()
|
||||
|
||||
|
||||
class FilterTestCase(JinjaTestCase):
|
||||
|
||||
def test_capitalize(self):
|
||||
tmpl = env.from_string('{{ "foo bar"|capitalize }}')
|
||||
assert tmpl.render() == 'Foo bar'
|
||||
|
||||
def test_center(self):
|
||||
tmpl = env.from_string('{{ "foo"|center(9) }}')
|
||||
assert tmpl.render() == ' foo '
|
||||
|
||||
def test_default(self):
|
||||
tmpl = env.from_string(
|
||||
"{{ missing|default('no') }}|{{ false|default('no') }}|"
|
||||
"{{ false|default('no', true) }}|{{ given|default('no') }}"
|
||||
)
|
||||
assert tmpl.render(given='yes') == 'no|False|no|yes'
|
||||
|
||||
def test_dictsort(self):
|
||||
tmpl = env.from_string(
|
||||
'{{ foo|dictsort }}|'
|
||||
'{{ foo|dictsort(true) }}|'
|
||||
'{{ foo|dictsort(false, "value") }}'
|
||||
)
|
||||
out = tmpl.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
|
||||
assert out == ("[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]|"
|
||||
"[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]|"
|
||||
"[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]")
|
||||
|
||||
def test_batch(self):
|
||||
tmpl = env.from_string("{{ foo|batch(3)|list }}|"
|
||||
"{{ foo|batch(3, 'X')|list }}")
|
||||
out = tmpl.render(foo=list(range(10)))
|
||||
assert out == ("[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
|
||||
"[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]")
|
||||
|
||||
def test_slice(self):
|
||||
tmpl = env.from_string('{{ foo|slice(3)|list }}|'
|
||||
'{{ foo|slice(3, "X")|list }}')
|
||||
out = tmpl.render(foo=list(range(10)))
|
||||
assert out == ("[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
|
||||
"[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]")
|
||||
|
||||
def test_escape(self):
|
||||
tmpl = env.from_string('''{{ '<">&'|escape }}''')
|
||||
out = tmpl.render()
|
||||
assert out == '<">&'
|
||||
|
||||
def test_striptags(self):
|
||||
tmpl = env.from_string('''{{ foo|striptags }}''')
|
||||
out = tmpl.render(foo=' <p>just a small \n <a href="#">'
|
||||
'example</a> link</p>\n<p>to a webpage</p> '
|
||||
'<!-- <p>and some commented stuff</p> -->')
|
||||
assert out == 'just a small example link to a webpage'
|
||||
|
||||
def test_filesizeformat(self):
|
||||
tmpl = env.from_string(
|
||||
'{{ 100|filesizeformat }}|'
|
||||
'{{ 1000|filesizeformat }}|'
|
||||
'{{ 1000000|filesizeformat }}|'
|
||||
'{{ 1000000000|filesizeformat }}|'
|
||||
'{{ 1000000000000|filesizeformat }}|'
|
||||
'{{ 100|filesizeformat(true) }}|'
|
||||
'{{ 1000|filesizeformat(true) }}|'
|
||||
'{{ 1000000|filesizeformat(true) }}|'
|
||||
'{{ 1000000000|filesizeformat(true) }}|'
|
||||
'{{ 1000000000000|filesizeformat(true) }}'
|
||||
)
|
||||
out = tmpl.render()
|
||||
self.assert_equal(out, (
|
||||
'100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|'
|
||||
'1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB'
|
||||
))
|
||||
|
||||
def test_filesizeformat_issue59(self):
|
||||
tmpl = env.from_string(
|
||||
'{{ 300|filesizeformat }}|'
|
||||
'{{ 3000|filesizeformat }}|'
|
||||
'{{ 3000000|filesizeformat }}|'
|
||||
'{{ 3000000000|filesizeformat }}|'
|
||||
'{{ 3000000000000|filesizeformat }}|'
|
||||
'{{ 300|filesizeformat(true) }}|'
|
||||
'{{ 3000|filesizeformat(true) }}|'
|
||||
'{{ 3000000|filesizeformat(true) }}'
|
||||
)
|
||||
out = tmpl.render()
|
||||
self.assert_equal(out, (
|
||||
'300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|'
|
||||
'2.9 KiB|2.9 MiB'
|
||||
))
|
||||
|
||||
|
||||
def test_first(self):
|
||||
tmpl = env.from_string('{{ foo|first }}')
|
||||
out = tmpl.render(foo=list(range(10)))
|
||||
assert out == '0'
|
||||
|
||||
def test_float(self):
|
||||
tmpl = env.from_string('{{ "42"|float }}|'
|
||||
'{{ "ajsghasjgd"|float }}|'
|
||||
'{{ "32.32"|float }}')
|
||||
out = tmpl.render()
|
||||
assert out == '42.0|0.0|32.32'
|
||||
|
||||
def test_format(self):
|
||||
tmpl = env.from_string('''{{ "%s|%s"|format("a", "b") }}''')
|
||||
out = tmpl.render()
|
||||
assert out == 'a|b'
|
||||
|
||||
def test_indent(self):
|
||||
tmpl = env.from_string('{{ foo|indent(2) }}|{{ foo|indent(2, true) }}')
|
||||
text = '\n'.join([' '.join(['foo', 'bar'] * 2)] * 2)
|
||||
out = tmpl.render(foo=text)
|
||||
assert out == ('foo bar foo bar\n foo bar foo bar| '
|
||||
'foo bar foo bar\n foo bar foo bar')
|
||||
|
||||
def test_int(self):
|
||||
tmpl = env.from_string('{{ "42"|int }}|{{ "ajsghasjgd"|int }}|'
|
||||
'{{ "32.32"|int }}')
|
||||
out = tmpl.render()
|
||||
assert out == '42|0|32'
|
||||
|
||||
def test_join(self):
|
||||
tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
|
||||
out = tmpl.render()
|
||||
assert out == '1|2|3'
|
||||
|
||||
env2 = Environment(autoescape=True)
|
||||
tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
|
||||
assert tmpl.render() == '<foo><span>foo</span>'
|
||||
|
||||
def test_join_attribute(self):
|
||||
class User(object):
|
||||
def __init__(self, username):
|
||||
self.username = username
|
||||
tmpl = env.from_string('''{{ users|join(', ', 'username') }}''')
|
||||
assert tmpl.render(users=map(User, ['foo', 'bar'])) == 'foo, bar'
|
||||
|
||||
def test_last(self):
|
||||
tmpl = env.from_string('''{{ foo|last }}''')
|
||||
out = tmpl.render(foo=list(range(10)))
|
||||
assert out == '9'
|
||||
|
||||
def test_length(self):
|
||||
tmpl = env.from_string('''{{ "hello world"|length }}''')
|
||||
out = tmpl.render()
|
||||
assert out == '11'
|
||||
|
||||
def test_lower(self):
|
||||
tmpl = env.from_string('''{{ "FOO"|lower }}''')
|
||||
out = tmpl.render()
|
||||
assert out == 'foo'
|
||||
|
||||
def test_pprint(self):
|
||||
from pprint import pformat
|
||||
tmpl = env.from_string('''{{ data|pprint }}''')
|
||||
data = list(range(1000))
|
||||
assert tmpl.render(data=data) == pformat(data)
|
||||
|
||||
def test_random(self):
|
||||
tmpl = env.from_string('''{{ seq|random }}''')
|
||||
seq = list(range(100))
|
||||
for _ in range(10):
|
||||
assert int(tmpl.render(seq=seq)) in seq
|
||||
|
||||
def test_reverse(self):
|
||||
tmpl = env.from_string('{{ "foobar"|reverse|join }}|'
|
||||
'{{ [1, 2, 3]|reverse|list }}')
|
||||
assert tmpl.render() == 'raboof|[3, 2, 1]'
|
||||
|
||||
def test_string(self):
|
||||
x = [1, 2, 3, 4, 5]
|
||||
tmpl = env.from_string('''{{ obj|string }}''')
|
||||
assert tmpl.render(obj=x) == text_type(x)
|
||||
|
||||
def test_title(self):
|
||||
tmpl = env.from_string('''{{ "foo bar"|title }}''')
|
||||
assert tmpl.render() == "Foo Bar"
|
||||
tmpl = env.from_string('''{{ "foo's bar"|title }}''')
|
||||
assert tmpl.render() == "Foo's Bar"
|
||||
tmpl = env.from_string('''{{ "foo bar"|title }}''')
|
||||
assert tmpl.render() == "Foo Bar"
|
||||
tmpl = env.from_string('''{{ "f bar f"|title }}''')
|
||||
assert tmpl.render() == "F Bar F"
|
||||
tmpl = env.from_string('''{{ "foo-bar"|title }}''')
|
||||
assert tmpl.render() == "Foo-Bar"
|
||||
tmpl = env.from_string('''{{ "foo\tbar"|title }}''')
|
||||
assert tmpl.render() == "Foo\tBar"
|
||||
|
||||
def test_truncate(self):
|
||||
tmpl = env.from_string(
|
||||
'{{ data|truncate(15, true, ">>>") }}|'
|
||||
'{{ data|truncate(15, false, ">>>") }}|'
|
||||
'{{ smalldata|truncate(15) }}'
|
||||
)
|
||||
out = tmpl.render(data='foobar baz bar' * 1000,
|
||||
smalldata='foobar baz bar')
|
||||
assert out == 'foobar baz barf>>>|foobar baz >>>|foobar baz bar'
|
||||
|
||||
def test_upper(self):
|
||||
tmpl = env.from_string('{{ "foo"|upper }}')
|
||||
assert tmpl.render() == 'FOO'
|
||||
|
||||
def test_urlize(self):
|
||||
tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
|
||||
assert tmpl.render() == 'foo <a href="http://www.example.com/">'\
|
||||
'http://www.example.com/</a> bar'
|
||||
|
||||
def test_wordcount(self):
|
||||
tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
|
||||
assert tmpl.render() == '3'
|
||||
|
||||
def test_block(self):
|
||||
tmpl = env.from_string('{% filter lower|escape %}<HEHE>{% endfilter %}')
|
||||
assert tmpl.render() == '<hehe>'
|
||||
|
||||
def test_chaining(self):
|
||||
tmpl = env.from_string('''{{ ['<foo>', '<bar>']|first|upper|escape }}''')
|
||||
assert tmpl.render() == '<FOO>'
|
||||
|
||||
def test_sum(self):
|
||||
tmpl = env.from_string('''{{ [1, 2, 3, 4, 5, 6]|sum }}''')
|
||||
assert tmpl.render() == '21'
|
||||
|
||||
def test_sum_attributes(self):
|
||||
tmpl = env.from_string('''{{ values|sum('value') }}''')
|
||||
assert tmpl.render(values=[
|
||||
{'value': 23},
|
||||
{'value': 1},
|
||||
{'value': 18},
|
||||
]) == '42'
|
||||
|
||||
def test_sum_attributes_nested(self):
|
||||
tmpl = env.from_string('''{{ values|sum('real.value') }}''')
|
||||
assert tmpl.render(values=[
|
||||
{'real': {'value': 23}},
|
||||
{'real': {'value': 1}},
|
||||
{'real': {'value': 18}},
|
||||
]) == '42'
|
||||
|
||||
def test_sum_attributes_tuple(self):
|
||||
tmpl = env.from_string('''{{ values.items()|sum('1') }}''')
|
||||
assert tmpl.render(values={
|
||||
'foo': 23,
|
||||
'bar': 1,
|
||||
'baz': 18,
|
||||
}) == '42'
|
||||
|
||||
def test_abs(self):
|
||||
tmpl = env.from_string('''{{ -1|abs }}|{{ 1|abs }}''')
|
||||
assert tmpl.render() == '1|1', tmpl.render()
|
||||
|
||||
def test_round_positive(self):
|
||||
tmpl = env.from_string('{{ 2.7|round }}|{{ 2.1|round }}|'
|
||||
"{{ 2.1234|round(3, 'floor') }}|"
|
||||
"{{ 2.1|round(0, 'ceil') }}")
|
||||
assert tmpl.render() == '3.0|2.0|2.123|3.0', tmpl.render()
|
||||
|
||||
def test_round_negative(self):
|
||||
tmpl = env.from_string('{{ 21.3|round(-1)}}|'
|
||||
"{{ 21.3|round(-1, 'ceil')}}|"
|
||||
"{{ 21.3|round(-1, 'floor')}}")
|
||||
assert tmpl.render() == '20.0|30.0|20.0',tmpl.render()
|
||||
|
||||
def test_xmlattr(self):
|
||||
tmpl = env.from_string("{{ {'foo': 42, 'bar': 23, 'fish': none, "
|
||||
"'spam': missing, 'blub:blub': '<?>'}|xmlattr }}")
|
||||
out = tmpl.render().split()
|
||||
assert len(out) == 3
|
||||
assert 'foo="42"' in out
|
||||
assert 'bar="23"' in out
|
||||
assert 'blub:blub="<?>"' in out
|
||||
|
||||
def test_sort1(self):
|
||||
tmpl = env.from_string('{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}')
|
||||
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
|
||||
|
||||
def test_sort2(self):
|
||||
tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
|
||||
assert tmpl.render() == 'AbcD'
|
||||
|
||||
def test_sort3(self):
|
||||
tmpl = env.from_string('''{{ ['foo', 'Bar', 'blah']|sort }}''')
|
||||
assert tmpl.render() == "['Bar', 'blah', 'foo']"
|
||||
|
||||
def test_sort4(self):
|
||||
@implements_to_string
|
||||
class Magic(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def __str__(self):
|
||||
return text_type(self.value)
|
||||
tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
|
||||
assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
|
||||
|
||||
def test_groupby(self):
|
||||
tmpl = env.from_string('''
|
||||
{%- for grouper, list in [{'foo': 1, 'bar': 2},
|
||||
{'foo': 2, 'bar': 3},
|
||||
{'foo': 1, 'bar': 1},
|
||||
{'foo': 3, 'bar': 4}]|groupby('foo') -%}
|
||||
{{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
|
||||
{%- endfor %}''')
|
||||
assert tmpl.render().split('|') == [
|
||||
"1: 1, 2: 1, 1",
|
||||
"2: 2, 3",
|
||||
"3: 3, 4",
|
||||
""
|
||||
]
|
||||
|
||||
def test_groupby_tuple_index(self):
|
||||
tmpl = env.from_string('''
|
||||
{%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
|
||||
{{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
|
||||
{%- endfor %}''')
|
||||
assert tmpl.render() == 'a:1:2|b:1|'
|
||||
|
||||
def test_groupby_multidot(self):
|
||||
class Date(object):
|
||||
def __init__(self, day, month, year):
|
||||
self.day = day
|
||||
self.month = month
|
||||
self.year = year
|
||||
class Article(object):
|
||||
def __init__(self, title, *date):
|
||||
self.date = Date(*date)
|
||||
self.title = title
|
||||
articles = [
|
||||
Article('aha', 1, 1, 1970),
|
||||
Article('interesting', 2, 1, 1970),
|
||||
Article('really?', 3, 1, 1970),
|
||||
Article('totally not', 1, 1, 1971)
|
||||
]
|
||||
tmpl = env.from_string('''
|
||||
{%- for year, list in articles|groupby('date.year') -%}
|
||||
{{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
|
||||
{%- endfor %}''')
|
||||
assert tmpl.render(articles=articles).split('|') == [
|
||||
'1970[aha][interesting][really?]',
|
||||
'1971[totally not]',
|
||||
''
|
||||
]
|
||||
|
||||
def test_filtertag(self):
|
||||
tmpl = env.from_string("{% filter upper|replace('FOO', 'foo') %}"
|
||||
"foobar{% endfilter %}")
|
||||
assert tmpl.render() == 'fooBAR'
|
||||
|
||||
def test_replace(self):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ string|replace("o", 42) }}')
|
||||
assert tmpl.render(string='<foo>') == '<f4242>'
|
||||
env = Environment(autoescape=True)
|
||||
tmpl = env.from_string('{{ string|replace("o", 42) }}')
|
||||
assert tmpl.render(string='<foo>') == '<f4242>'
|
||||
tmpl = env.from_string('{{ string|replace("<", 42) }}')
|
||||
assert tmpl.render(string='<foo>') == '42foo>'
|
||||
tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
|
||||
assert tmpl.render(string=Markup('foo')) == 'f>x<>x<'
|
||||
|
||||
def test_forceescape(self):
|
||||
tmpl = env.from_string('{{ x|forceescape }}')
|
||||
assert tmpl.render(x=Markup('<div />')) == u'<div />'
|
||||
|
||||
def test_safe(self):
|
||||
env = Environment(autoescape=True)
|
||||
tmpl = env.from_string('{{ "<div>foo</div>"|safe }}')
|
||||
assert tmpl.render() == '<div>foo</div>'
|
||||
tmpl = env.from_string('{{ "<div>foo</div>" }}')
|
||||
assert tmpl.render() == '<div>foo</div>'
|
||||
|
||||
def test_urlencode(self):
|
||||
env = Environment(autoescape=True)
|
||||
tmpl = env.from_string('{{ "Hello, world!"|urlencode }}')
|
||||
assert tmpl.render() == 'Hello%2C%20world%21'
|
||||
tmpl = env.from_string('{{ o|urlencode }}')
|
||||
assert tmpl.render(o=u"Hello, world\u203d") == "Hello%2C%20world%E2%80%BD"
|
||||
assert tmpl.render(o=(("f", 1),)) == "f=1"
|
||||
assert tmpl.render(o=(('f', 1), ("z", 2))) == "f=1&z=2"
|
||||
assert tmpl.render(o=((u"\u203d", 1),)) == "%E2%80%BD=1"
|
||||
assert tmpl.render(o={u"\u203d": 1}) == "%E2%80%BD=1"
|
||||
assert tmpl.render(o={0: 1}) == "0=1"
|
||||
|
||||
def test_simple_map(self):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}')
|
||||
self.assertEqual(tmpl.render(), '6')
|
||||
|
||||
def test_attribute_map(self):
|
||||
class User(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
env = Environment()
|
||||
users = [
|
||||
User('john'),
|
||||
User('jane'),
|
||||
User('mike'),
|
||||
]
|
||||
tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
|
||||
self.assertEqual(tmpl.render(users=users), 'john|jane|mike')
|
||||
|
||||
def test_empty_map(self):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ none|map("upper")|list }}')
|
||||
self.assertEqual(tmpl.render(), '[]')
|
||||
|
||||
def test_simple_select(self):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')
|
||||
self.assertEqual(tmpl.render(), '1|3|5')
|
||||
|
||||
def test_bool_select(self):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}')
|
||||
self.assertEqual(tmpl.render(), '1|2|3|4|5')
|
||||
|
||||
def test_simple_reject(self):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}')
|
||||
self.assertEqual(tmpl.render(), '2|4')
|
||||
|
||||
def test_bool_reject(self):
|
||||
env = Environment()
|
||||
tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}')
|
||||
self.assertEqual(tmpl.render(), 'None|False|0')
|
||||
|
||||
def test_simple_select_attr(self):
|
||||
class User(object):
|
||||
def __init__(self, name, is_active):
|
||||
self.name = name
|
||||
self.is_active = is_active
|
||||
env = Environment()
|
||||
users = [
|
||||
User('john', True),
|
||||
User('jane', True),
|
||||
User('mike', False),
|
||||
]
|
||||
tmpl = env.from_string('{{ users|selectattr("is_active")|'
|
||||
'map(attribute="name")|join("|") }}')
|
||||
self.assertEqual(tmpl.render(users=users), 'john|jane')
|
||||
|
||||
def test_simple_reject_attr(self):
|
||||
class User(object):
|
||||
def __init__(self, name, is_active):
|
||||
self.name = name
|
||||
self.is_active = is_active
|
||||
env = Environment()
|
||||
users = [
|
||||
User('john', True),
|
||||
User('jane', True),
|
||||
User('mike', False),
|
||||
]
|
||||
tmpl = env.from_string('{{ users|rejectattr("is_active")|'
|
||||
'map(attribute="name")|join("|") }}')
|
||||
self.assertEqual(tmpl.render(users=users), 'mike')
|
||||
|
||||
def test_func_select_attr(self):
|
||||
class User(object):
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
env = Environment()
|
||||
users = [
|
||||
User(1, 'john'),
|
||||
User(2, 'jane'),
|
||||
User(3, 'mike'),
|
||||
]
|
||||
tmpl = env.from_string('{{ users|selectattr("id", "odd")|'
|
||||
'map(attribute="name")|join("|") }}')
|
||||
self.assertEqual(tmpl.render(users=users), 'john|mike')
|
||||
|
||||
def test_func_reject_attr(self):
|
||||
class User(object):
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
env = Environment()
|
||||
users = [
|
||||
User(1, 'john'),
|
||||
User(2, 'jane'),
|
||||
User(3, 'mike'),
|
||||
]
|
||||
tmpl = env.from_string('{{ users|rejectattr("id", "odd")|'
|
||||
'map(attribute="name")|join("|") }}')
|
||||
self.assertEqual(tmpl.render(users=users), 'jane')
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(FilterTestCase))
|
||||
return suite
|
||||
Vendored
-157
@@ -1,157 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.imports
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests the import features (with includes).
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Environment, DictLoader
|
||||
from jinja2.exceptions import TemplateNotFound, TemplatesNotFound
|
||||
|
||||
|
||||
test_env = Environment(loader=DictLoader(dict(
|
||||
module='{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}',
|
||||
header='[{{ foo }}|{{ 23 }}]',
|
||||
o_printer='({{ o }})'
|
||||
)))
|
||||
test_env.globals['bar'] = 23
|
||||
|
||||
|
||||
class ImportsTestCase(JinjaTestCase):
|
||||
|
||||
def test_context_imports(self):
|
||||
t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
|
||||
assert t.render(foo=42) == '[|23]'
|
||||
t = test_env.from_string('{% import "module" as m without context %}{{ m.test() }}')
|
||||
assert t.render(foo=42) == '[|23]'
|
||||
t = test_env.from_string('{% import "module" as m with context %}{{ m.test() }}')
|
||||
assert t.render(foo=42) == '[42|23]'
|
||||
t = test_env.from_string('{% from "module" import test %}{{ test() }}')
|
||||
assert t.render(foo=42) == '[|23]'
|
||||
t = test_env.from_string('{% from "module" import test without context %}{{ test() }}')
|
||||
assert t.render(foo=42) == '[|23]'
|
||||
t = test_env.from_string('{% from "module" import test with context %}{{ test() }}')
|
||||
assert t.render(foo=42) == '[42|23]'
|
||||
|
||||
def test_trailing_comma(self):
|
||||
test_env.from_string('{% from "foo" import bar, baz with context %}')
|
||||
test_env.from_string('{% from "foo" import bar, baz, with context %}')
|
||||
test_env.from_string('{% from "foo" import bar, with context %}')
|
||||
test_env.from_string('{% from "foo" import bar, with, context %}')
|
||||
test_env.from_string('{% from "foo" import bar, with with context %}')
|
||||
|
||||
def test_exports(self):
|
||||
m = test_env.from_string('''
|
||||
{% macro toplevel() %}...{% endmacro %}
|
||||
{% macro __private() %}...{% endmacro %}
|
||||
{% set variable = 42 %}
|
||||
{% for item in [1] %}
|
||||
{% macro notthere() %}{% endmacro %}
|
||||
{% endfor %}
|
||||
''').module
|
||||
assert m.toplevel() == '...'
|
||||
assert not hasattr(m, '__missing')
|
||||
assert m.variable == 42
|
||||
assert not hasattr(m, 'notthere')
|
||||
|
||||
|
||||
class IncludesTestCase(JinjaTestCase):
|
||||
|
||||
def test_context_include(self):
|
||||
t = test_env.from_string('{% include "header" %}')
|
||||
assert t.render(foo=42) == '[42|23]'
|
||||
t = test_env.from_string('{% include "header" with context %}')
|
||||
assert t.render(foo=42) == '[42|23]'
|
||||
t = test_env.from_string('{% include "header" without context %}')
|
||||
assert t.render(foo=42) == '[|23]'
|
||||
|
||||
def test_choice_includes(self):
|
||||
t = test_env.from_string('{% include ["missing", "header"] %}')
|
||||
assert t.render(foo=42) == '[42|23]'
|
||||
|
||||
t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}')
|
||||
assert t.render(foo=42) == ''
|
||||
|
||||
t = test_env.from_string('{% include ["missing", "missing2"] %}')
|
||||
self.assert_raises(TemplateNotFound, t.render)
|
||||
try:
|
||||
t.render()
|
||||
except TemplatesNotFound as e:
|
||||
assert e.templates == ['missing', 'missing2']
|
||||
assert e.name == 'missing2'
|
||||
else:
|
||||
assert False, 'thou shalt raise'
|
||||
|
||||
def test_includes(t, **ctx):
|
||||
ctx['foo'] = 42
|
||||
assert t.render(ctx) == '[42|23]'
|
||||
|
||||
t = test_env.from_string('{% include ["missing", "header"] %}')
|
||||
test_includes(t)
|
||||
t = test_env.from_string('{% include x %}')
|
||||
test_includes(t, x=['missing', 'header'])
|
||||
t = test_env.from_string('{% include [x, "header"] %}')
|
||||
test_includes(t, x='missing')
|
||||
t = test_env.from_string('{% include x %}')
|
||||
test_includes(t, x='header')
|
||||
t = test_env.from_string('{% include x %}')
|
||||
test_includes(t, x='header')
|
||||
t = test_env.from_string('{% include [x] %}')
|
||||
test_includes(t, x='header')
|
||||
|
||||
def test_include_ignoring_missing(self):
|
||||
t = test_env.from_string('{% include "missing" %}')
|
||||
self.assert_raises(TemplateNotFound, t.render)
|
||||
for extra in '', 'with context', 'without context':
|
||||
t = test_env.from_string('{% include "missing" ignore missing ' +
|
||||
extra + ' %}')
|
||||
assert t.render() == ''
|
||||
|
||||
def test_context_include_with_overrides(self):
|
||||
env = Environment(loader=DictLoader(dict(
|
||||
main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
|
||||
item="{{ item }}"
|
||||
)))
|
||||
assert env.get_template("main").render() == "123"
|
||||
|
||||
def test_included_block_override(self):
|
||||
env = Environment(loader=DictLoader(dict(
|
||||
main="{% extends 'base' %}{% block b %}1337{% endblock %}",
|
||||
base="{% include 'inc' %}",
|
||||
inc="{% block b %}42{% endblock %}"
|
||||
)))
|
||||
assert env.get_template("main").render() == "1337"
|
||||
|
||||
def test_included_block_override_with_super(self):
|
||||
env = Environment(loader=DictLoader(dict(
|
||||
main="{% extends 'base' %}{% block b %}1337|{{ super() }}{% endblock %}",
|
||||
base="{% include 'inc' %}",
|
||||
inc="{% block b %}42{% endblock %}"
|
||||
)))
|
||||
assert env.get_template("main").render() == "1337|42"
|
||||
|
||||
def test_unoptimized_scopes(self):
|
||||
t = test_env.from_string("""
|
||||
{% macro outer(o) %}
|
||||
{% macro inner() %}
|
||||
{% include "o_printer" %}
|
||||
{% endmacro %}
|
||||
{{ inner() }}
|
||||
{% endmacro %}
|
||||
{{ outer("FOO") }}
|
||||
""")
|
||||
assert t.render().strip() == '(FOO)'
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(ImportsTestCase))
|
||||
suite.addTest(unittest.makeSuite(IncludesTestCase))
|
||||
return suite
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests the template inheritance feature.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Environment, DictLoader, TemplateError
|
||||
|
||||
|
||||
LAYOUTTEMPLATE = '''\
|
||||
|{% block block1 %}block 1 from layout{% endblock %}
|
||||
|{% block block2 %}block 2 from layout{% endblock %}
|
||||
|{% block block3 %}
|
||||
{% block block4 %}nested block 4 from layout{% endblock %}
|
||||
{% endblock %}|'''
|
||||
|
||||
LEVEL1TEMPLATE = '''\
|
||||
{% extends "layout" %}
|
||||
{% block block1 %}block 1 from level1{% endblock %}'''
|
||||
|
||||
LEVEL2TEMPLATE = '''\
|
||||
{% extends "level1" %}
|
||||
{% block block2 %}{% block block5 %}nested block 5 from level2{%
|
||||
endblock %}{% endblock %}'''
|
||||
|
||||
LEVEL3TEMPLATE = '''\
|
||||
{% extends "level2" %}
|
||||
{% block block5 %}block 5 from level3{% endblock %}
|
||||
{% block block4 %}block 4 from level3{% endblock %}
|
||||
'''
|
||||
|
||||
LEVEL4TEMPLATE = '''\
|
||||
{% extends "level3" %}
|
||||
{% block block3 %}block 3 from level4{% endblock %}
|
||||
'''
|
||||
|
||||
WORKINGTEMPLATE = '''\
|
||||
{% extends "layout" %}
|
||||
{% block block1 %}
|
||||
{% if false %}
|
||||
{% block block2 %}
|
||||
this should workd
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
'''
|
||||
|
||||
DOUBLEEXTENDS = '''\
|
||||
{% extends "layout" %}
|
||||
{% extends "layout" %}
|
||||
{% block block1 %}
|
||||
{% if false %}
|
||||
{% block block2 %}
|
||||
this should workd
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
'''
|
||||
|
||||
|
||||
env = Environment(loader=DictLoader({
|
||||
'layout': LAYOUTTEMPLATE,
|
||||
'level1': LEVEL1TEMPLATE,
|
||||
'level2': LEVEL2TEMPLATE,
|
||||
'level3': LEVEL3TEMPLATE,
|
||||
'level4': LEVEL4TEMPLATE,
|
||||
'working': WORKINGTEMPLATE,
|
||||
'doublee': DOUBLEEXTENDS,
|
||||
}), trim_blocks=True)
|
||||
|
||||
|
||||
class InheritanceTestCase(JinjaTestCase):
|
||||
|
||||
def test_layout(self):
|
||||
tmpl = env.get_template('layout')
|
||||
assert tmpl.render() == ('|block 1 from layout|block 2 from '
|
||||
'layout|nested block 4 from layout|')
|
||||
|
||||
def test_level1(self):
|
||||
tmpl = env.get_template('level1')
|
||||
assert tmpl.render() == ('|block 1 from level1|block 2 from '
|
||||
'layout|nested block 4 from layout|')
|
||||
|
||||
def test_level2(self):
|
||||
tmpl = env.get_template('level2')
|
||||
assert tmpl.render() == ('|block 1 from level1|nested block 5 from '
|
||||
'level2|nested block 4 from layout|')
|
||||
|
||||
def test_level3(self):
|
||||
tmpl = env.get_template('level3')
|
||||
assert tmpl.render() == ('|block 1 from level1|block 5 from level3|'
|
||||
'block 4 from level3|')
|
||||
|
||||
def test_level4(sel):
|
||||
tmpl = env.get_template('level4')
|
||||
assert tmpl.render() == ('|block 1 from level1|block 5 from '
|
||||
'level3|block 3 from level4|')
|
||||
|
||||
def test_super(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'a': '{% block intro %}INTRO{% endblock %}|'
|
||||
'BEFORE|{% block data %}INNER{% endblock %}|AFTER',
|
||||
'b': '{% extends "a" %}{% block data %}({{ '
|
||||
'super() }}){% endblock %}',
|
||||
'c': '{% extends "b" %}{% block intro %}--{{ '
|
||||
'super() }}--{% endblock %}\n{% block data '
|
||||
'%}[{{ super() }}]{% endblock %}'
|
||||
}))
|
||||
tmpl = env.get_template('c')
|
||||
assert tmpl.render() == '--INTRO--|BEFORE|[(INNER)]|AFTER'
|
||||
|
||||
def test_working(self):
|
||||
tmpl = env.get_template('working')
|
||||
|
||||
def test_reuse_blocks(self):
|
||||
tmpl = env.from_string('{{ self.foo() }}|{% block foo %}42'
|
||||
'{% endblock %}|{{ self.foo() }}')
|
||||
assert tmpl.render() == '42|42|42'
|
||||
|
||||
def test_preserve_blocks(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'a': '{% if false %}{% block x %}A{% endblock %}{% endif %}{{ self.x() }}',
|
||||
'b': '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}'
|
||||
}))
|
||||
tmpl = env.get_template('b')
|
||||
assert tmpl.render() == 'BA'
|
||||
|
||||
def test_dynamic_inheritance(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'master1': 'MASTER1{% block x %}{% endblock %}',
|
||||
'master2': 'MASTER2{% block x %}{% endblock %}',
|
||||
'child': '{% extends master %}{% block x %}CHILD{% endblock %}'
|
||||
}))
|
||||
tmpl = env.get_template('child')
|
||||
for m in range(1, 3):
|
||||
assert tmpl.render(master='master%d' % m) == 'MASTER%dCHILD' % m
|
||||
|
||||
def test_multi_inheritance(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'master1': 'MASTER1{% block x %}{% endblock %}',
|
||||
'master2': 'MASTER2{% block x %}{% endblock %}',
|
||||
'child': '''{% if master %}{% extends master %}{% else %}{% extends
|
||||
'master1' %}{% endif %}{% block x %}CHILD{% endblock %}'''
|
||||
}))
|
||||
tmpl = env.get_template('child')
|
||||
assert tmpl.render(master='master2') == 'MASTER2CHILD'
|
||||
assert tmpl.render(master='master1') == 'MASTER1CHILD'
|
||||
assert tmpl.render() == 'MASTER1CHILD'
|
||||
|
||||
def test_scoped_block(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'master.html': '{% for item in seq %}[{% block item scoped %}'
|
||||
'{% endblock %}]{% endfor %}'
|
||||
}))
|
||||
t = env.from_string('{% extends "master.html" %}{% block item %}'
|
||||
'{{ item }}{% endblock %}')
|
||||
assert t.render(seq=list(range(5))) == '[0][1][2][3][4]'
|
||||
|
||||
def test_super_in_scoped_block(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'master.html': '{% for item in seq %}[{% block item scoped %}'
|
||||
'{{ item }}{% endblock %}]{% endfor %}'
|
||||
}))
|
||||
t = env.from_string('{% extends "master.html" %}{% block item %}'
|
||||
'{{ super() }}|{{ item * 2 }}{% endblock %}')
|
||||
assert t.render(seq=list(range(5))) == '[0|0][1|2][2|4][3|6][4|8]'
|
||||
|
||||
def test_scoped_block_after_inheritance(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'layout.html': '''
|
||||
{% block useless %}{% endblock %}
|
||||
''',
|
||||
'index.html': '''
|
||||
{%- extends 'layout.html' %}
|
||||
{% from 'helpers.html' import foo with context %}
|
||||
{% block useless %}
|
||||
{% for x in [1, 2, 3] %}
|
||||
{% block testing scoped %}
|
||||
{{ foo(x) }}
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
''',
|
||||
'helpers.html': '''
|
||||
{% macro foo(x) %}{{ the_foo + x }}{% endmacro %}
|
||||
'''
|
||||
}))
|
||||
rv = env.get_template('index.html').render(the_foo=42).split()
|
||||
assert rv == ['43', '44', '45']
|
||||
|
||||
|
||||
class BugFixTestCase(JinjaTestCase):
|
||||
|
||||
def test_fixed_macro_scoping_bug(self):
|
||||
assert Environment(loader=DictLoader({
|
||||
'test.html': '''\
|
||||
{% extends 'details.html' %}
|
||||
|
||||
{% macro my_macro() %}
|
||||
my_macro
|
||||
{% endmacro %}
|
||||
|
||||
{% block inner_box %}
|
||||
{{ my_macro() }}
|
||||
{% endblock %}
|
||||
''',
|
||||
'details.html': '''\
|
||||
{% extends 'standard.html' %}
|
||||
|
||||
{% macro my_macro() %}
|
||||
my_macro
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
{% block outer_box %}
|
||||
outer_box
|
||||
{% block inner_box %}
|
||||
inner_box
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
''',
|
||||
'standard.html': '''
|
||||
{% block content %} {% endblock %}
|
||||
'''
|
||||
})).get_template("test.html").render().split() == [u'outer_box', u'my_macro']
|
||||
|
||||
def test_double_extends(self):
|
||||
"""Ensures that a template with more than 1 {% extends ... %} usage
|
||||
raises a ``TemplateError``.
|
||||
"""
|
||||
try:
|
||||
tmpl = env.get_template('doublee')
|
||||
except Exception as e:
|
||||
assert isinstance(e, TemplateError)
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(InheritanceTestCase))
|
||||
suite.addTest(unittest.makeSuite(BugFixTestCase))
|
||||
return suite
|
||||
Vendored
-593
@@ -1,593 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.lexnparse
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All the unittests regarding lexing, parsing and syntax.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Environment, Template, TemplateSyntaxError, \
|
||||
UndefinedError, nodes
|
||||
from jinja2._compat import next, iteritems, text_type, PY2
|
||||
from jinja2.lexer import Token, TokenStream, TOKEN_EOF, \
|
||||
TOKEN_BLOCK_BEGIN, TOKEN_BLOCK_END
|
||||
|
||||
env = Environment()
|
||||
|
||||
|
||||
# how does a string look like in jinja syntax?
|
||||
if PY2:
|
||||
def jinja_string_repr(string):
|
||||
return repr(string)[1:]
|
||||
else:
|
||||
jinja_string_repr = repr
|
||||
|
||||
|
||||
class TokenStreamTestCase(JinjaTestCase):
|
||||
test_tokens = [Token(1, TOKEN_BLOCK_BEGIN, ''),
|
||||
Token(2, TOKEN_BLOCK_END, ''),
|
||||
]
|
||||
|
||||
def test_simple(self):
|
||||
ts = TokenStream(self.test_tokens, "foo", "bar")
|
||||
assert ts.current.type is TOKEN_BLOCK_BEGIN
|
||||
assert bool(ts)
|
||||
assert not bool(ts.eos)
|
||||
next(ts)
|
||||
assert ts.current.type is TOKEN_BLOCK_END
|
||||
assert bool(ts)
|
||||
assert not bool(ts.eos)
|
||||
next(ts)
|
||||
assert ts.current.type is TOKEN_EOF
|
||||
assert not bool(ts)
|
||||
assert bool(ts.eos)
|
||||
|
||||
def test_iter(self):
|
||||
token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")]
|
||||
assert token_types == ['block_begin', 'block_end', ]
|
||||
|
||||
|
||||
class LexerTestCase(JinjaTestCase):
|
||||
|
||||
def test_raw1(self):
|
||||
tmpl = env.from_string('{% raw %}foo{% endraw %}|'
|
||||
'{%raw%}{{ bar }}|{% baz %}{% endraw %}')
|
||||
assert tmpl.render() == 'foo|{{ bar }}|{% baz %}'
|
||||
|
||||
def test_raw2(self):
|
||||
tmpl = env.from_string('1 {%- raw -%} 2 {%- endraw -%} 3')
|
||||
assert tmpl.render() == '123'
|
||||
|
||||
def test_balancing(self):
|
||||
env = Environment('{%', '%}', '${', '}')
|
||||
tmpl = env.from_string('''{% for item in seq
|
||||
%}${{'foo': item}|upper}{% endfor %}''')
|
||||
assert tmpl.render(seq=list(range(3))) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}"
|
||||
|
||||
def test_comments(self):
|
||||
env = Environment('<!--', '-->', '{', '}')
|
||||
tmpl = env.from_string('''\
|
||||
<ul>
|
||||
<!--- for item in seq -->
|
||||
<li>{item}</li>
|
||||
<!--- endfor -->
|
||||
</ul>''')
|
||||
assert tmpl.render(seq=list(range(3))) == ("<ul>\n <li>0</li>\n "
|
||||
"<li>1</li>\n <li>2</li>\n</ul>")
|
||||
|
||||
def test_string_escapes(self):
|
||||
for char in u'\0', u'\u2668', u'\xe4', u'\t', u'\r', u'\n':
|
||||
tmpl = env.from_string('{{ %s }}' % jinja_string_repr(char))
|
||||
assert tmpl.render() == char
|
||||
assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u'\u2668'
|
||||
|
||||
def test_bytefallback(self):
|
||||
from pprint import pformat
|
||||
tmpl = env.from_string(u'''{{ 'foo'|pprint }}|{{ 'bär'|pprint }}''')
|
||||
assert tmpl.render() == pformat('foo') + '|' + pformat(u'bär')
|
||||
|
||||
def test_operators(self):
|
||||
from jinja2.lexer import operators
|
||||
for test, expect in iteritems(operators):
|
||||
if test in '([{}])':
|
||||
continue
|
||||
stream = env.lexer.tokenize('{{ %s }}' % test)
|
||||
next(stream)
|
||||
assert stream.current.type == expect
|
||||
|
||||
def test_normalizing(self):
|
||||
for seq in '\r', '\r\n', '\n':
|
||||
env = Environment(newline_sequence=seq)
|
||||
tmpl = env.from_string('1\n2\r\n3\n4\n')
|
||||
result = tmpl.render()
|
||||
assert result.replace(seq, 'X') == '1X2X3X4'
|
||||
|
||||
def test_trailing_newline(self):
|
||||
for keep in [True, False]:
|
||||
env = Environment(keep_trailing_newline=keep)
|
||||
for template,expected in [
|
||||
('', {}),
|
||||
('no\nnewline', {}),
|
||||
('with\nnewline\n', {False: 'with\nnewline'}),
|
||||
('with\nseveral\n\n\n', {False: 'with\nseveral\n\n'}),
|
||||
]:
|
||||
tmpl = env.from_string(template)
|
||||
expect = expected.get(keep, template)
|
||||
result = tmpl.render()
|
||||
assert result == expect, (keep, template, result, expect)
|
||||
|
||||
class ParserTestCase(JinjaTestCase):
|
||||
|
||||
def test_php_syntax(self):
|
||||
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
|
||||
tmpl = env.from_string('''\
|
||||
<!-- I'm a comment, I'm not interesting -->\
|
||||
<? for item in seq -?>
|
||||
<?= item ?>
|
||||
<?- endfor ?>''')
|
||||
assert tmpl.render(seq=list(range(5))) == '01234'
|
||||
|
||||
def test_erb_syntax(self):
|
||||
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>')
|
||||
tmpl = env.from_string('''\
|
||||
<%# I'm a comment, I'm not interesting %>\
|
||||
<% for item in seq -%>
|
||||
<%= item %>
|
||||
<%- endfor %>''')
|
||||
assert tmpl.render(seq=list(range(5))) == '01234'
|
||||
|
||||
def test_comment_syntax(self):
|
||||
env = Environment('<!--', '-->', '${', '}', '<!--#', '-->')
|
||||
tmpl = env.from_string('''\
|
||||
<!--# I'm a comment, I'm not interesting -->\
|
||||
<!-- for item in seq --->
|
||||
${item}
|
||||
<!--- endfor -->''')
|
||||
assert tmpl.render(seq=list(range(5))) == '01234'
|
||||
|
||||
def test_balancing(self):
|
||||
tmpl = env.from_string('''{{{'foo':'bar'}.foo}}''')
|
||||
assert tmpl.render() == 'bar'
|
||||
|
||||
def test_start_comment(self):
|
||||
tmpl = env.from_string('''{# foo comment
|
||||
and bar comment #}
|
||||
{% macro blub() %}foo{% endmacro %}
|
||||
{{ blub() }}''')
|
||||
assert tmpl.render().strip() == 'foo'
|
||||
|
||||
def test_line_syntax(self):
|
||||
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
|
||||
tmpl = env.from_string('''\
|
||||
<%# regular comment %>
|
||||
% for item in seq:
|
||||
${item}
|
||||
% endfor''')
|
||||
assert [int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()] == \
|
||||
list(range(5))
|
||||
|
||||
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
|
||||
tmpl = env.from_string('''\
|
||||
<%# regular comment %>
|
||||
% for item in seq:
|
||||
${item} ## the rest of the stuff
|
||||
% endfor''')
|
||||
assert [int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()] == \
|
||||
list(range(5))
|
||||
|
||||
def test_line_syntax_priority(self):
|
||||
# XXX: why is the whitespace there in front of the newline?
|
||||
env = Environment('{%', '%}', '${', '}', '/*', '*/', '##', '#')
|
||||
tmpl = env.from_string('''\
|
||||
/* ignore me.
|
||||
I'm a multiline comment */
|
||||
## for item in seq:
|
||||
* ${item} # this is just extra stuff
|
||||
## endfor''')
|
||||
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n* 2'
|
||||
env = Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
|
||||
tmpl = env.from_string('''\
|
||||
/* ignore me.
|
||||
I'm a multiline comment */
|
||||
# for item in seq:
|
||||
* ${item} ## this is just extra stuff
|
||||
## extra stuff i just want to ignore
|
||||
# endfor''')
|
||||
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n\n* 2'
|
||||
|
||||
def test_error_messages(self):
|
||||
def assert_error(code, expected):
|
||||
try:
|
||||
Template(code)
|
||||
except TemplateSyntaxError as e:
|
||||
assert str(e) == expected, 'unexpected error message'
|
||||
else:
|
||||
assert False, 'that was supposed to be an error'
|
||||
|
||||
assert_error('{% for item in seq %}...{% endif %}',
|
||||
"Encountered unknown tag 'endif'. Jinja was looking "
|
||||
"for the following tags: 'endfor' or 'else'. The "
|
||||
"innermost block that needs to be closed is 'for'.")
|
||||
assert_error('{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}',
|
||||
"Encountered unknown tag 'endfor'. Jinja was looking for "
|
||||
"the following tags: 'elif' or 'else' or 'endif'. The "
|
||||
"innermost block that needs to be closed is 'if'.")
|
||||
assert_error('{% if foo %}',
|
||||
"Unexpected end of template. Jinja was looking for the "
|
||||
"following tags: 'elif' or 'else' or 'endif'. The "
|
||||
"innermost block that needs to be closed is 'if'.")
|
||||
assert_error('{% for item in seq %}',
|
||||
"Unexpected end of template. Jinja was looking for the "
|
||||
"following tags: 'endfor' or 'else'. The innermost block "
|
||||
"that needs to be closed is 'for'.")
|
||||
assert_error('{% block foo-bar-baz %}',
|
||||
"Block names in Jinja have to be valid Python identifiers "
|
||||
"and may not contain hyphens, use an underscore instead.")
|
||||
assert_error('{% unknown_tag %}',
|
||||
"Encountered unknown tag 'unknown_tag'.")
|
||||
|
||||
|
||||
class SyntaxTestCase(JinjaTestCase):
|
||||
|
||||
def test_call(self):
|
||||
env = Environment()
|
||||
env.globals['foo'] = lambda a, b, c, e, g: a + b + c + e + g
|
||||
tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}")
|
||||
assert tmpl.render() == 'abdfh'
|
||||
|
||||
def test_slicing(self):
|
||||
tmpl = env.from_string('{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}')
|
||||
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
|
||||
|
||||
def test_attr(self):
|
||||
tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
|
||||
assert tmpl.render(foo={'bar': 42}) == '42|42'
|
||||
|
||||
def test_subscript(self):
|
||||
tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}")
|
||||
assert tmpl.render(foo=[0, 1, 2]) == '0|2'
|
||||
|
||||
def test_tuple(self):
|
||||
tmpl = env.from_string('{{ () }}|{{ (1,) }}|{{ (1, 2) }}')
|
||||
assert tmpl.render() == '()|(1,)|(1, 2)'
|
||||
|
||||
def test_math(self):
|
||||
tmpl = env.from_string('{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}')
|
||||
assert tmpl.render() == '1.5|8'
|
||||
|
||||
def test_div(self):
|
||||
tmpl = env.from_string('{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}')
|
||||
assert tmpl.render() == '1|1.5|1'
|
||||
|
||||
def test_unary(self):
|
||||
tmpl = env.from_string('{{ +3 }}|{{ -3 }}')
|
||||
assert tmpl.render() == '3|-3'
|
||||
|
||||
def test_concat(self):
|
||||
tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
|
||||
assert tmpl.render() == '[1, 2]foo'
|
||||
|
||||
def test_compare(self):
|
||||
tmpl = env.from_string('{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|'
|
||||
'{{ 2 == 2 }}|{{ 1 <= 1 }}')
|
||||
assert tmpl.render() == 'True|True|True|True|True'
|
||||
|
||||
def test_inop(self):
|
||||
tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
|
||||
assert tmpl.render() == 'True|False'
|
||||
|
||||
def test_literals(self):
|
||||
tmpl = env.from_string('{{ [] }}|{{ {} }}|{{ () }}')
|
||||
assert tmpl.render().lower() == '[]|{}|()'
|
||||
|
||||
def test_bool(self):
|
||||
tmpl = env.from_string('{{ true and false }}|{{ false '
|
||||
'or true }}|{{ not false }}')
|
||||
assert tmpl.render() == 'False|True|True'
|
||||
|
||||
def test_grouping(self):
|
||||
tmpl = env.from_string('{{ (true and false) or (false and true) and not false }}')
|
||||
assert tmpl.render() == 'False'
|
||||
|
||||
def test_django_attr(self):
|
||||
tmpl = env.from_string('{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}')
|
||||
assert tmpl.render() == '1|1'
|
||||
|
||||
def test_conditional_expression(self):
|
||||
tmpl = env.from_string('''{{ 0 if true else 1 }}''')
|
||||
assert tmpl.render() == '0'
|
||||
|
||||
def test_short_conditional_expression(self):
|
||||
tmpl = env.from_string('<{{ 1 if false }}>')
|
||||
assert tmpl.render() == '<>'
|
||||
|
||||
tmpl = env.from_string('<{{ (1 if false).bar }}>')
|
||||
self.assert_raises(UndefinedError, tmpl.render)
|
||||
|
||||
def test_filter_priority(self):
|
||||
tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
|
||||
assert tmpl.render() == 'FOOBAR'
|
||||
|
||||
def test_function_calls(self):
|
||||
tests = [
|
||||
(True, '*foo, bar'),
|
||||
(True, '*foo, *bar'),
|
||||
(True, '*foo, bar=42'),
|
||||
(True, '**foo, *bar'),
|
||||
(True, '**foo, bar'),
|
||||
(False, 'foo, bar'),
|
||||
(False, 'foo, bar=42'),
|
||||
(False, 'foo, bar=23, *args'),
|
||||
(False, 'a, b=c, *d, **e'),
|
||||
(False, '*foo, **bar')
|
||||
]
|
||||
for should_fail, sig in tests:
|
||||
if should_fail:
|
||||
self.assert_raises(TemplateSyntaxError,
|
||||
env.from_string, '{{ foo(%s) }}' % sig)
|
||||
else:
|
||||
env.from_string('foo(%s)' % sig)
|
||||
|
||||
def test_tuple_expr(self):
|
||||
for tmpl in [
|
||||
'{{ () }}',
|
||||
'{{ (1, 2) }}',
|
||||
'{{ (1, 2,) }}',
|
||||
'{{ 1, }}',
|
||||
'{{ 1, 2 }}',
|
||||
'{% for foo, bar in seq %}...{% endfor %}',
|
||||
'{% for x in foo, bar %}...{% endfor %}',
|
||||
'{% for x in foo, %}...{% endfor %}'
|
||||
]:
|
||||
assert env.from_string(tmpl)
|
||||
|
||||
def test_trailing_comma(self):
|
||||
tmpl = env.from_string('{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}')
|
||||
assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}'
|
||||
|
||||
def test_block_end_name(self):
|
||||
env.from_string('{% block foo %}...{% endblock foo %}')
|
||||
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||
'{% block x %}{% endblock y %}')
|
||||
|
||||
def test_constant_casing(self):
|
||||
for const in True, False, None:
|
||||
tmpl = env.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % (
|
||||
str(const), str(const).lower(), str(const).upper()
|
||||
))
|
||||
assert tmpl.render() == '%s|%s|' % (const, const)
|
||||
|
||||
def test_test_chaining(self):
|
||||
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||
'{{ foo is string is sequence }}')
|
||||
assert env.from_string('{{ 42 is string or 42 is number }}'
|
||||
).render() == 'True'
|
||||
|
||||
def test_string_concatenation(self):
|
||||
tmpl = env.from_string('{{ "foo" "bar" "baz" }}')
|
||||
assert tmpl.render() == 'foobarbaz'
|
||||
|
||||
def test_notin(self):
|
||||
bar = range(100)
|
||||
tmpl = env.from_string('''{{ not 42 in bar }}''')
|
||||
assert tmpl.render(bar=bar) == text_type(not 42 in bar)
|
||||
|
||||
def test_implicit_subscribed_tuple(self):
|
||||
class Foo(object):
|
||||
def __getitem__(self, x):
|
||||
return x
|
||||
t = env.from_string('{{ foo[1, 2] }}')
|
||||
assert t.render(foo=Foo()) == u'(1, 2)'
|
||||
|
||||
def test_raw2(self):
|
||||
tmpl = env.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
|
||||
assert tmpl.render() == '{{ FOO }} and {% BAR %}'
|
||||
|
||||
def test_const(self):
|
||||
tmpl = env.from_string('{{ true }}|{{ false }}|{{ none }}|'
|
||||
'{{ none is defined }}|{{ missing is defined }}')
|
||||
assert tmpl.render() == 'True|False|None|True|False'
|
||||
|
||||
def test_neg_filter_priority(self):
|
||||
node = env.parse('{{ -1|foo }}')
|
||||
assert isinstance(node.body[0].nodes[0], nodes.Filter)
|
||||
assert isinstance(node.body[0].nodes[0].node, nodes.Neg)
|
||||
|
||||
def test_const_assign(self):
|
||||
constass1 = '''{% set true = 42 %}'''
|
||||
constass2 = '''{% for none in seq %}{% endfor %}'''
|
||||
for tmpl in constass1, constass2:
|
||||
self.assert_raises(TemplateSyntaxError, env.from_string, tmpl)
|
||||
|
||||
def test_localset(self):
|
||||
tmpl = env.from_string('''{% set foo = 0 %}\
|
||||
{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
|
||||
{{ foo }}''')
|
||||
assert tmpl.render() == '0'
|
||||
|
||||
def test_parse_unary(self):
|
||||
tmpl = env.from_string('{{ -foo["bar"] }}')
|
||||
assert tmpl.render(foo={'bar': 42}) == '-42'
|
||||
tmpl = env.from_string('{{ -foo["bar"]|abs }}')
|
||||
assert tmpl.render(foo={'bar': 42}) == '42'
|
||||
|
||||
|
||||
class LstripBlocksTestCase(JinjaTestCase):
|
||||
|
||||
def test_lstrip(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' {% if True %}\n {% endif %}''')
|
||||
assert tmpl.render() == "\n"
|
||||
|
||||
def test_lstrip_trim(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string(''' {% if True %}\n {% endif %}''')
|
||||
assert tmpl.render() == ""
|
||||
|
||||
def test_no_lstrip(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' {%+ if True %}\n {%+ endif %}''')
|
||||
assert tmpl.render() == " \n "
|
||||
|
||||
def test_lstrip_endline(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' hello{% if True %}\n goodbye{% endif %}''')
|
||||
assert tmpl.render() == " hello\n goodbye"
|
||||
|
||||
def test_lstrip_inline(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' {% if True %}hello {% endif %}''')
|
||||
assert tmpl.render() == 'hello '
|
||||
|
||||
def test_lstrip_nested(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' {% if True %}a {% if True %}b {% endif %}c {% endif %}''')
|
||||
assert tmpl.render() == 'a b c '
|
||||
|
||||
def test_lstrip_left_chars(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' abc {% if True %}
|
||||
hello{% endif %}''')
|
||||
assert tmpl.render() == ' abc \n hello'
|
||||
|
||||
def test_lstrip_embeded_strings(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' {% set x = " {% str %} " %}{{ x }}''')
|
||||
assert tmpl.render() == ' {% str %} '
|
||||
|
||||
def test_lstrip_preserve_leading_newlines(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string('''\n\n\n{% set hello = 1 %}''')
|
||||
assert tmpl.render() == '\n\n\n'
|
||||
|
||||
def test_lstrip_comment(self):
|
||||
env = Environment(lstrip_blocks=True, trim_blocks=False)
|
||||
tmpl = env.from_string(''' {# if True #}
|
||||
hello
|
||||
{#endif#}''')
|
||||
assert tmpl.render() == '\nhello\n'
|
||||
|
||||
def test_lstrip_angle_bracket_simple(self):
|
||||
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string(''' <% if True %>hello <% endif %>''')
|
||||
assert tmpl.render() == 'hello '
|
||||
|
||||
def test_lstrip_angle_bracket_comment(self):
|
||||
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string(''' <%# if True %>hello <%# endif %>''')
|
||||
assert tmpl.render() == 'hello '
|
||||
|
||||
def test_lstrip_angle_bracket(self):
|
||||
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<%# regular comment %>
|
||||
<% for item in seq %>
|
||||
${item} ## the rest of the stuff
|
||||
<% endfor %>''')
|
||||
assert tmpl.render(seq=range(5)) == \
|
||||
''.join('%s\n' % x for x in range(5))
|
||||
|
||||
def test_lstrip_angle_bracket_compact(self):
|
||||
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<%#regular comment%>
|
||||
<%for item in seq%>
|
||||
${item} ## the rest of the stuff
|
||||
<%endfor%>''')
|
||||
assert tmpl.render(seq=range(5)) == \
|
||||
''.join('%s\n' % x for x in range(5))
|
||||
|
||||
def test_php_syntax_with_manual(self):
|
||||
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<!-- I'm a comment, I'm not interesting -->
|
||||
<? for item in seq -?>
|
||||
<?= item ?>
|
||||
<?- endfor ?>''')
|
||||
assert tmpl.render(seq=range(5)) == '01234'
|
||||
|
||||
def test_php_syntax(self):
|
||||
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<!-- I'm a comment, I'm not interesting -->
|
||||
<? for item in seq ?>
|
||||
<?= item ?>
|
||||
<? endfor ?>''')
|
||||
assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
|
||||
|
||||
def test_php_syntax_compact(self):
|
||||
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<!-- I'm a comment, I'm not interesting -->
|
||||
<?for item in seq?>
|
||||
<?=item?>
|
||||
<?endfor?>''')
|
||||
assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
|
||||
|
||||
def test_erb_syntax(self):
|
||||
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
#env.from_string('')
|
||||
#for n,r in env.lexer.rules.iteritems():
|
||||
# print n
|
||||
#print env.lexer.rules['root'][0][0].pattern
|
||||
#print "'%s'" % tmpl.render(seq=range(5))
|
||||
tmpl = env.from_string('''\
|
||||
<%# I'm a comment, I'm not interesting %>
|
||||
<% for item in seq %>
|
||||
<%= item %>
|
||||
<% endfor %>
|
||||
''')
|
||||
assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
|
||||
|
||||
def test_erb_syntax_with_manual(self):
|
||||
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<%# I'm a comment, I'm not interesting %>
|
||||
<% for item in seq -%>
|
||||
<%= item %>
|
||||
<%- endfor %>''')
|
||||
assert tmpl.render(seq=range(5)) == '01234'
|
||||
|
||||
def test_erb_syntax_no_lstrip(self):
|
||||
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<%# I'm a comment, I'm not interesting %>
|
||||
<%+ for item in seq -%>
|
||||
<%= item %>
|
||||
<%- endfor %>''')
|
||||
assert tmpl.render(seq=range(5)) == ' 01234'
|
||||
|
||||
def test_comment_syntax(self):
|
||||
env = Environment('<!--', '-->', '${', '}', '<!--#', '-->',
|
||||
lstrip_blocks=True, trim_blocks=True)
|
||||
tmpl = env.from_string('''\
|
||||
<!--# I'm a comment, I'm not interesting -->\
|
||||
<!-- for item in seq --->
|
||||
${item}
|
||||
<!--- endfor -->''')
|
||||
assert tmpl.render(seq=range(5)) == '01234'
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TokenStreamTestCase))
|
||||
suite.addTest(unittest.makeSuite(LexerTestCase))
|
||||
suite.addTest(unittest.makeSuite(ParserTestCase))
|
||||
suite.addTest(unittest.makeSuite(SyntaxTestCase))
|
||||
suite.addTest(unittest.makeSuite(LstripBlocksTestCase))
|
||||
return suite
|
||||
Vendored
-226
@@ -1,226 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.loader
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the loaders.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase, dict_loader, \
|
||||
package_loader, filesystem_loader, function_loader, \
|
||||
choice_loader, prefix_loader
|
||||
|
||||
from jinja2 import Environment, loaders
|
||||
from jinja2._compat import PYPY, PY2
|
||||
from jinja2.loaders import split_template_path
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
|
||||
|
||||
class LoaderTestCase(JinjaTestCase):
|
||||
|
||||
def test_dict_loader(self):
|
||||
env = Environment(loader=dict_loader)
|
||||
tmpl = env.get_template('justdict.html')
|
||||
assert tmpl.render().strip() == 'FOO'
|
||||
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||
|
||||
def test_package_loader(self):
|
||||
env = Environment(loader=package_loader)
|
||||
tmpl = env.get_template('test.html')
|
||||
assert tmpl.render().strip() == 'BAR'
|
||||
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||
|
||||
def test_filesystem_loader(self):
|
||||
env = Environment(loader=filesystem_loader)
|
||||
tmpl = env.get_template('test.html')
|
||||
assert tmpl.render().strip() == 'BAR'
|
||||
tmpl = env.get_template('foo/test.html')
|
||||
assert tmpl.render().strip() == 'FOO'
|
||||
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||
|
||||
def test_choice_loader(self):
|
||||
env = Environment(loader=choice_loader)
|
||||
tmpl = env.get_template('justdict.html')
|
||||
assert tmpl.render().strip() == 'FOO'
|
||||
tmpl = env.get_template('test.html')
|
||||
assert tmpl.render().strip() == 'BAR'
|
||||
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||
|
||||
def test_function_loader(self):
|
||||
env = Environment(loader=function_loader)
|
||||
tmpl = env.get_template('justfunction.html')
|
||||
assert tmpl.render().strip() == 'FOO'
|
||||
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
|
||||
|
||||
def test_prefix_loader(self):
|
||||
env = Environment(loader=prefix_loader)
|
||||
tmpl = env.get_template('a/test.html')
|
||||
assert tmpl.render().strip() == 'BAR'
|
||||
tmpl = env.get_template('b/justdict.html')
|
||||
assert tmpl.render().strip() == 'FOO'
|
||||
self.assert_raises(TemplateNotFound, env.get_template, 'missing')
|
||||
|
||||
def test_caching(self):
|
||||
changed = False
|
||||
class TestLoader(loaders.BaseLoader):
|
||||
def get_source(self, environment, template):
|
||||
return u'foo', None, lambda: not changed
|
||||
env = Environment(loader=TestLoader(), cache_size=-1)
|
||||
tmpl = env.get_template('template')
|
||||
assert tmpl is env.get_template('template')
|
||||
changed = True
|
||||
assert tmpl is not env.get_template('template')
|
||||
changed = False
|
||||
|
||||
env = Environment(loader=TestLoader(), cache_size=0)
|
||||
assert env.get_template('template') \
|
||||
is not env.get_template('template')
|
||||
|
||||
env = Environment(loader=TestLoader(), cache_size=2)
|
||||
t1 = env.get_template('one')
|
||||
t2 = env.get_template('two')
|
||||
assert t2 is env.get_template('two')
|
||||
assert t1 is env.get_template('one')
|
||||
t3 = env.get_template('three')
|
||||
assert 'one' in env.cache
|
||||
assert 'two' not in env.cache
|
||||
assert 'three' in env.cache
|
||||
|
||||
def test_dict_loader_cache_invalidates(self):
|
||||
mapping = {'foo': "one"}
|
||||
env = Environment(loader=loaders.DictLoader(mapping))
|
||||
assert env.get_template('foo').render() == "one"
|
||||
mapping['foo'] = "two"
|
||||
assert env.get_template('foo').render() == "two"
|
||||
|
||||
def test_split_template_path(self):
|
||||
assert split_template_path('foo/bar') == ['foo', 'bar']
|
||||
assert split_template_path('./foo/bar') == ['foo', 'bar']
|
||||
self.assert_raises(TemplateNotFound, split_template_path, '../foo')
|
||||
|
||||
|
||||
class ModuleLoaderTestCase(JinjaTestCase):
|
||||
archive = None
|
||||
|
||||
def compile_down(self, zip='deflated', py_compile=False):
|
||||
super(ModuleLoaderTestCase, self).setup()
|
||||
log = []
|
||||
self.reg_env = Environment(loader=prefix_loader)
|
||||
if zip is not None:
|
||||
self.archive = tempfile.mkstemp(suffix='.zip')[1]
|
||||
else:
|
||||
self.archive = tempfile.mkdtemp()
|
||||
self.reg_env.compile_templates(self.archive, zip=zip,
|
||||
log_function=log.append,
|
||||
py_compile=py_compile)
|
||||
self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
|
||||
return ''.join(log)
|
||||
|
||||
def teardown(self):
|
||||
super(ModuleLoaderTestCase, self).teardown()
|
||||
if hasattr(self, 'mod_env'):
|
||||
if os.path.isfile(self.archive):
|
||||
os.remove(self.archive)
|
||||
else:
|
||||
shutil.rmtree(self.archive)
|
||||
self.archive = None
|
||||
|
||||
def test_log(self):
|
||||
log = self.compile_down()
|
||||
assert 'Compiled "a/foo/test.html" as ' \
|
||||
'tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a' in log
|
||||
assert 'Finished compiling templates' in log
|
||||
assert 'Could not compile "a/syntaxerror.html": ' \
|
||||
'Encountered unknown tag \'endif\'' in log
|
||||
|
||||
def _test_common(self):
|
||||
tmpl1 = self.reg_env.get_template('a/test.html')
|
||||
tmpl2 = self.mod_env.get_template('a/test.html')
|
||||
assert tmpl1.render() == tmpl2.render()
|
||||
|
||||
tmpl1 = self.reg_env.get_template('b/justdict.html')
|
||||
tmpl2 = self.mod_env.get_template('b/justdict.html')
|
||||
assert tmpl1.render() == tmpl2.render()
|
||||
|
||||
def test_deflated_zip_compile(self):
|
||||
self.compile_down(zip='deflated')
|
||||
self._test_common()
|
||||
|
||||
def test_stored_zip_compile(self):
|
||||
self.compile_down(zip='stored')
|
||||
self._test_common()
|
||||
|
||||
def test_filesystem_compile(self):
|
||||
self.compile_down(zip=None)
|
||||
self._test_common()
|
||||
|
||||
def test_weak_references(self):
|
||||
self.compile_down()
|
||||
tmpl = self.mod_env.get_template('a/test.html')
|
||||
key = loaders.ModuleLoader.get_template_key('a/test.html')
|
||||
name = self.mod_env.loader.module.__name__
|
||||
|
||||
assert hasattr(self.mod_env.loader.module, key)
|
||||
assert name in sys.modules
|
||||
|
||||
# unset all, ensure the module is gone from sys.modules
|
||||
self.mod_env = tmpl = None
|
||||
|
||||
try:
|
||||
import gc
|
||||
gc.collect()
|
||||
except:
|
||||
pass
|
||||
|
||||
assert name not in sys.modules
|
||||
|
||||
# This test only makes sense on non-pypy python 2
|
||||
if PY2 and not PYPY:
|
||||
def test_byte_compilation(self):
|
||||
log = self.compile_down(py_compile=True)
|
||||
assert 'Byte-compiled "a/test.html"' in log
|
||||
tmpl1 = self.mod_env.get_template('a/test.html')
|
||||
mod = self.mod_env.loader.module. \
|
||||
tmpl_3c4ddf650c1a73df961a6d3d2ce2752f1b8fd490
|
||||
assert mod.__file__.endswith('.pyc')
|
||||
|
||||
def test_choice_loader(self):
|
||||
log = self.compile_down()
|
||||
|
||||
self.mod_env.loader = loaders.ChoiceLoader([
|
||||
self.mod_env.loader,
|
||||
loaders.DictLoader({'DICT_SOURCE': 'DICT_TEMPLATE'})
|
||||
])
|
||||
|
||||
tmpl1 = self.mod_env.get_template('a/test.html')
|
||||
self.assert_equal(tmpl1.render(), 'BAR')
|
||||
tmpl2 = self.mod_env.get_template('DICT_SOURCE')
|
||||
self.assert_equal(tmpl2.render(), 'DICT_TEMPLATE')
|
||||
|
||||
def test_prefix_loader(self):
|
||||
log = self.compile_down()
|
||||
|
||||
self.mod_env.loader = loaders.PrefixLoader({
|
||||
'MOD': self.mod_env.loader,
|
||||
'DICT': loaders.DictLoader({'test.html': 'DICT_TEMPLATE'})
|
||||
})
|
||||
|
||||
tmpl1 = self.mod_env.get_template('MOD/a/test.html')
|
||||
self.assert_equal(tmpl1.render(), 'BAR')
|
||||
tmpl2 = self.mod_env.get_template('DICT/test.html')
|
||||
self.assert_equal(tmpl2.render(), 'DICT_TEMPLATE')
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(LoaderTestCase))
|
||||
suite.addTest(unittest.makeSuite(ModuleLoaderTestCase))
|
||||
return suite
|
||||
-279
@@ -1,279 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.regression
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests corner cases and bugs.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
|
||||
TemplateNotFound, PrefixLoader
|
||||
from jinja2._compat import text_type
|
||||
|
||||
env = Environment()
|
||||
|
||||
|
||||
class CornerTestCase(JinjaTestCase):
|
||||
|
||||
def test_assigned_scoping(self):
|
||||
t = env.from_string('''
|
||||
{%- for item in (1, 2, 3, 4) -%}
|
||||
[{{ item }}]
|
||||
{%- endfor %}
|
||||
{{- item -}}
|
||||
''')
|
||||
assert t.render(item=42) == '[1][2][3][4]42'
|
||||
|
||||
t = env.from_string('''
|
||||
{%- for item in (1, 2, 3, 4) -%}
|
||||
[{{ item }}]
|
||||
{%- endfor %}
|
||||
{%- set item = 42 %}
|
||||
{{- item -}}
|
||||
''')
|
||||
assert t.render() == '[1][2][3][4]42'
|
||||
|
||||
t = env.from_string('''
|
||||
{%- set item = 42 %}
|
||||
{%- for item in (1, 2, 3, 4) -%}
|
||||
[{{ item }}]
|
||||
{%- endfor %}
|
||||
{{- item -}}
|
||||
''')
|
||||
assert t.render() == '[1][2][3][4]42'
|
||||
|
||||
def test_closure_scoping(self):
|
||||
t = env.from_string('''
|
||||
{%- set wrapper = "<FOO>" %}
|
||||
{%- for item in (1, 2, 3, 4) %}
|
||||
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
|
||||
{{- wrapper() }}
|
||||
{%- endfor %}
|
||||
{{- wrapper -}}
|
||||
''')
|
||||
assert t.render() == '[1][2][3][4]<FOO>'
|
||||
|
||||
t = env.from_string('''
|
||||
{%- for item in (1, 2, 3, 4) %}
|
||||
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
|
||||
{{- wrapper() }}
|
||||
{%- endfor %}
|
||||
{%- set wrapper = "<FOO>" %}
|
||||
{{- wrapper -}}
|
||||
''')
|
||||
assert t.render() == '[1][2][3][4]<FOO>'
|
||||
|
||||
t = env.from_string('''
|
||||
{%- for item in (1, 2, 3, 4) %}
|
||||
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
|
||||
{{- wrapper() }}
|
||||
{%- endfor %}
|
||||
{{- wrapper -}}
|
||||
''')
|
||||
assert t.render(wrapper=23) == '[1][2][3][4]23'
|
||||
|
||||
|
||||
class BugTestCase(JinjaTestCase):
|
||||
|
||||
def test_keyword_folding(self):
|
||||
env = Environment()
|
||||
env.filters['testing'] = lambda value, some: value + some
|
||||
assert env.from_string("{{ 'test'|testing(some='stuff') }}") \
|
||||
.render() == 'teststuff'
|
||||
|
||||
def test_extends_output_bugs(self):
|
||||
env = Environment(loader=DictLoader({
|
||||
'parent.html': '(({% block title %}{% endblock %}))'
|
||||
}))
|
||||
|
||||
t = env.from_string('{% if expr %}{% extends "parent.html" %}{% endif %}'
|
||||
'[[{% block title %}title{% endblock %}]]'
|
||||
'{% for item in [1, 2, 3] %}({{ item }}){% endfor %}')
|
||||
assert t.render(expr=False) == '[[title]](1)(2)(3)'
|
||||
assert t.render(expr=True) == '((title))'
|
||||
|
||||
def test_urlize_filter_escaping(self):
|
||||
tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
|
||||
assert tmpl.render() == '<a href="http://www.example.org/<foo">http://www.example.org/<foo</a>'
|
||||
|
||||
def test_loop_call_loop(self):
|
||||
tmpl = env.from_string('''
|
||||
|
||||
{% macro test() %}
|
||||
{{ caller() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% for num1 in range(5) %}
|
||||
{% call test() %}
|
||||
{% for num2 in range(10) %}
|
||||
{{ loop.index }}
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endfor %}
|
||||
|
||||
''')
|
||||
|
||||
assert tmpl.render().split() == [text_type(x) for x in range(1, 11)] * 5
|
||||
|
||||
def test_weird_inline_comment(self):
|
||||
env = Environment(line_statement_prefix='%')
|
||||
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||
'% for item in seq {# missing #}\n...% endfor')
|
||||
|
||||
def test_old_macro_loop_scoping_bug(self):
|
||||
tmpl = env.from_string('{% for i in (1, 2) %}{{ i }}{% endfor %}'
|
||||
'{% macro i() %}3{% endmacro %}{{ i() }}')
|
||||
assert tmpl.render() == '123'
|
||||
|
||||
def test_partial_conditional_assignments(self):
|
||||
tmpl = env.from_string('{% if b %}{% set a = 42 %}{% endif %}{{ a }}')
|
||||
assert tmpl.render(a=23) == '23'
|
||||
assert tmpl.render(b=True) == '42'
|
||||
|
||||
def test_stacked_locals_scoping_bug(self):
|
||||
env = Environment(line_statement_prefix='#')
|
||||
t = env.from_string('''\
|
||||
# for j in [1, 2]:
|
||||
# set x = 1
|
||||
# for i in [1, 2]:
|
||||
# print x
|
||||
# if i % 2 == 0:
|
||||
# set x = x + 1
|
||||
# endif
|
||||
# endfor
|
||||
# endfor
|
||||
# if a
|
||||
# print 'A'
|
||||
# elif b
|
||||
# print 'B'
|
||||
# elif c == d
|
||||
# print 'C'
|
||||
# else
|
||||
# print 'D'
|
||||
# endif
|
||||
''')
|
||||
assert t.render(a=0, b=False, c=42, d=42.0) == '1111C'
|
||||
|
||||
def test_stacked_locals_scoping_bug_twoframe(self):
|
||||
t = Template('''
|
||||
{% set x = 1 %}
|
||||
{% for item in foo %}
|
||||
{% if item == 1 %}
|
||||
{% set x = 2 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ x }}
|
||||
''')
|
||||
rv = t.render(foo=[1]).strip()
|
||||
assert rv == u'1'
|
||||
|
||||
def test_call_with_args(self):
|
||||
t = Template("""{% macro dump_users(users) -%}
|
||||
<ul>
|
||||
{%- for user in users -%}
|
||||
<li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
{%- endmacro -%}
|
||||
|
||||
{% call(user) dump_users(list_of_user) -%}
|
||||
<dl>
|
||||
<dl>Realname</dl>
|
||||
<dd>{{ user.realname|e }}</dd>
|
||||
<dl>Description</dl>
|
||||
<dd>{{ user.description }}</dd>
|
||||
</dl>
|
||||
{% endcall %}""")
|
||||
|
||||
assert [x.strip() for x in t.render(list_of_user=[{
|
||||
'username':'apo',
|
||||
'realname':'something else',
|
||||
'description':'test'
|
||||
}]).splitlines()] == [
|
||||
u'<ul><li><p>apo</p><dl>',
|
||||
u'<dl>Realname</dl>',
|
||||
u'<dd>something else</dd>',
|
||||
u'<dl>Description</dl>',
|
||||
u'<dd>test</dd>',
|
||||
u'</dl>',
|
||||
u'</li></ul>'
|
||||
]
|
||||
|
||||
def test_empty_if_condition_fails(self):
|
||||
self.assert_raises(TemplateSyntaxError, Template, '{% if %}....{% endif %}')
|
||||
self.assert_raises(TemplateSyntaxError, Template, '{% if foo %}...{% elif %}...{% endif %}')
|
||||
self.assert_raises(TemplateSyntaxError, Template, '{% for x in %}..{% endfor %}')
|
||||
|
||||
def test_recursive_loop_bug(self):
|
||||
tpl1 = Template("""
|
||||
{% for p in foo recursive%}
|
||||
{{p.bar}}
|
||||
{% for f in p.fields recursive%}
|
||||
{{f.baz}}
|
||||
{{p.bar}}
|
||||
{% if f.rec %}
|
||||
{{ loop(f.sub) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
""")
|
||||
|
||||
tpl2 = Template("""
|
||||
{% for p in foo%}
|
||||
{{p.bar}}
|
||||
{% for f in p.fields recursive%}
|
||||
{{f.baz}}
|
||||
{{p.bar}}
|
||||
{% if f.rec %}
|
||||
{{ loop(f.sub) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
""")
|
||||
|
||||
def test_else_loop_bug(self):
|
||||
t = Template('''
|
||||
{% for x in y %}
|
||||
{{ loop.index0 }}
|
||||
{% else %}
|
||||
{% for i in range(3) %}{{ i }}{% endfor %}
|
||||
{% endfor %}
|
||||
''')
|
||||
self.assertEqual(t.render(y=[]).strip(), '012')
|
||||
|
||||
def test_correct_prefix_loader_name(self):
|
||||
env = Environment(loader=PrefixLoader({
|
||||
'foo': DictLoader({})
|
||||
}))
|
||||
try:
|
||||
env.get_template('foo/bar.html')
|
||||
except TemplateNotFound as e:
|
||||
assert e.name == 'foo/bar.html'
|
||||
else:
|
||||
assert False, 'expected error here'
|
||||
|
||||
def test_contextfunction_callable_classes(self):
|
||||
from jinja2.utils import contextfunction
|
||||
class CallableClass(object):
|
||||
@contextfunction
|
||||
def __call__(self, ctx):
|
||||
return ctx.resolve('hello')
|
||||
|
||||
tpl = Template("""{{ callableclass() }}""")
|
||||
output = tpl.render(callableclass = CallableClass(), hello = 'TEST')
|
||||
expected = 'TEST'
|
||||
|
||||
self.assert_equal(output, expected)
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(CornerTestCase))
|
||||
suite.addTest(unittest.makeSuite(BugTestCase))
|
||||
return suite
|
||||
@@ -1,3 +0,0 @@
|
||||
Before
|
||||
{{ fail() }}
|
||||
After
|
||||
@@ -1 +0,0 @@
|
||||
FOO
|
||||
@@ -1,4 +0,0 @@
|
||||
Foo
|
||||
{% for item in broken %}
|
||||
...
|
||||
{% endif %}
|
||||
@@ -1 +0,0 @@
|
||||
BAR
|
||||
Vendored
-166
@@ -1,166 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.security
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Checks the sandbox and other security features.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Environment
|
||||
from jinja2.sandbox import SandboxedEnvironment, \
|
||||
ImmutableSandboxedEnvironment, unsafe
|
||||
from jinja2 import Markup, escape
|
||||
from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
|
||||
TemplateRuntimeError
|
||||
from jinja2._compat import text_type
|
||||
|
||||
|
||||
class PrivateStuff(object):
|
||||
|
||||
def bar(self):
|
||||
return 23
|
||||
|
||||
@unsafe
|
||||
def foo(self):
|
||||
return 42
|
||||
|
||||
def __repr__(self):
|
||||
return 'PrivateStuff'
|
||||
|
||||
|
||||
class PublicStuff(object):
|
||||
bar = lambda self: 23
|
||||
_foo = lambda self: 42
|
||||
|
||||
def __repr__(self):
|
||||
return 'PublicStuff'
|
||||
|
||||
|
||||
class SandboxTestCase(JinjaTestCase):
|
||||
|
||||
def test_unsafe(self):
|
||||
env = SandboxedEnvironment()
|
||||
self.assert_raises(SecurityError, env.from_string("{{ foo.foo() }}").render,
|
||||
foo=PrivateStuff())
|
||||
self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()), '23')
|
||||
|
||||
self.assert_raises(SecurityError, env.from_string("{{ foo._foo() }}").render,
|
||||
foo=PublicStuff())
|
||||
self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()), '23')
|
||||
self.assert_equal(env.from_string("{{ foo.__class__ }}").render(foo=42), '')
|
||||
self.assert_equal(env.from_string("{{ foo.func_code }}").render(foo=lambda:None), '')
|
||||
# security error comes from __class__ already.
|
||||
self.assert_raises(SecurityError, env.from_string(
|
||||
"{{ foo.__class__.__subclasses__() }}").render, foo=42)
|
||||
|
||||
def test_immutable_environment(self):
|
||||
env = ImmutableSandboxedEnvironment()
|
||||
self.assert_raises(SecurityError, env.from_string(
|
||||
'{{ [].append(23) }}').render)
|
||||
self.assert_raises(SecurityError, env.from_string(
|
||||
'{{ {1:2}.clear() }}').render)
|
||||
|
||||
def test_restricted(self):
|
||||
env = SandboxedEnvironment()
|
||||
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||
"{% for item.attribute in seq %}...{% endfor %}")
|
||||
self.assert_raises(TemplateSyntaxError, env.from_string,
|
||||
"{% for foo, bar.baz in seq %}...{% endfor %}")
|
||||
|
||||
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 == text_type(escape(unsafe)) + text_type(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_template_data(self):
|
||||
env = Environment(autoescape=True)
|
||||
t = env.from_string('{% macro say_hello(name) %}'
|
||||
'<p>Hello {{ name }}!</p>{% endmacro %}'
|
||||
'{{ say_hello("<blink>foo</blink>") }}')
|
||||
escaped_out = '<p>Hello <blink>foo</blink>!</p>'
|
||||
assert t.render() == escaped_out
|
||||
assert text_type(t.module) == escaped_out
|
||||
assert escape(t.module) == escaped_out
|
||||
assert t.module.say_hello('<blink>foo</blink>') == escaped_out
|
||||
assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out
|
||||
|
||||
def test_attr_filter(self):
|
||||
env = SandboxedEnvironment()
|
||||
tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
|
||||
self.assert_raises(SecurityError, tmpl.render, cls=int)
|
||||
|
||||
def test_binary_operator_intercepting(self):
|
||||
def disable_op(left, right):
|
||||
raise TemplateRuntimeError('that operator so does not work')
|
||||
for expr, ctx, rv in ('1 + 2', {}, '3'), ('a + 2', {'a': 2}, '4'):
|
||||
env = SandboxedEnvironment()
|
||||
env.binop_table['+'] = disable_op
|
||||
t = env.from_string('{{ %s }}' % expr)
|
||||
assert t.render(ctx) == rv
|
||||
env.intercepted_binops = frozenset(['+'])
|
||||
t = env.from_string('{{ %s }}' % expr)
|
||||
try:
|
||||
t.render(ctx)
|
||||
except TemplateRuntimeError as e:
|
||||
pass
|
||||
else:
|
||||
self.fail('expected runtime error')
|
||||
|
||||
def test_unary_operator_intercepting(self):
|
||||
def disable_op(arg):
|
||||
raise TemplateRuntimeError('that operator so does not work')
|
||||
for expr, ctx, rv in ('-1', {}, '-1'), ('-a', {'a': 2}, '-2'):
|
||||
env = SandboxedEnvironment()
|
||||
env.unop_table['-'] = disable_op
|
||||
t = env.from_string('{{ %s }}' % expr)
|
||||
assert t.render(ctx) == rv
|
||||
env.intercepted_unops = frozenset(['-'])
|
||||
t = env.from_string('{{ %s }}' % expr)
|
||||
try:
|
||||
t.render(ctx)
|
||||
except TemplateRuntimeError as e:
|
||||
pass
|
||||
else:
|
||||
self.fail('expected runtime error')
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(SandboxTestCase))
|
||||
return suite
|
||||
Vendored
-93
@@ -1,93 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.tests
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Who tests the tests?
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import unittest
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2 import Markup, Environment
|
||||
|
||||
env = Environment()
|
||||
|
||||
|
||||
class TestsTestCase(JinjaTestCase):
|
||||
|
||||
def test_defined(self):
|
||||
tmpl = env.from_string('{{ missing is defined }}|{{ true is defined }}')
|
||||
assert tmpl.render() == 'False|True'
|
||||
|
||||
def test_even(self):
|
||||
tmpl = env.from_string('''{{ 1 is even }}|{{ 2 is even }}''')
|
||||
assert tmpl.render() == 'False|True'
|
||||
|
||||
def test_odd(self):
|
||||
tmpl = env.from_string('''{{ 1 is odd }}|{{ 2 is odd }}''')
|
||||
assert tmpl.render() == 'True|False'
|
||||
|
||||
def test_lower(self):
|
||||
tmpl = env.from_string('''{{ "foo" is lower }}|{{ "FOO" is lower }}''')
|
||||
assert tmpl.render() == 'True|False'
|
||||
|
||||
def test_typechecks(self):
|
||||
tmpl = env.from_string('''
|
||||
{{ 42 is undefined }}
|
||||
{{ 42 is defined }}
|
||||
{{ 42 is none }}
|
||||
{{ none is none }}
|
||||
{{ 42 is number }}
|
||||
{{ 42 is string }}
|
||||
{{ "foo" is string }}
|
||||
{{ "foo" is sequence }}
|
||||
{{ [1] is sequence }}
|
||||
{{ range is callable }}
|
||||
{{ 42 is callable }}
|
||||
{{ range(5) is iterable }}
|
||||
{{ {} is mapping }}
|
||||
{{ mydict is mapping }}
|
||||
{{ [] is mapping }}
|
||||
''')
|
||||
class MyDict(dict):
|
||||
pass
|
||||
assert tmpl.render(mydict=MyDict()).split() == [
|
||||
'False', 'True', 'False', 'True', 'True', 'False',
|
||||
'True', 'True', 'True', 'True', 'False', 'True',
|
||||
'True', 'True', 'False'
|
||||
]
|
||||
|
||||
def test_sequence(self):
|
||||
tmpl = env.from_string(
|
||||
'{{ [1, 2, 3] is sequence }}|'
|
||||
'{{ "foo" is sequence }}|'
|
||||
'{{ 42 is sequence }}'
|
||||
)
|
||||
assert tmpl.render() == 'True|True|False'
|
||||
|
||||
def test_upper(self):
|
||||
tmpl = env.from_string('{{ "FOO" is upper }}|{{ "foo" is upper }}')
|
||||
assert tmpl.render() == 'True|False'
|
||||
|
||||
def test_sameas(self):
|
||||
tmpl = env.from_string('{{ foo is sameas false }}|'
|
||||
'{{ 0 is sameas false }}')
|
||||
assert tmpl.render(foo=False) == 'True|False'
|
||||
|
||||
def test_no_paren_for_arg1(self):
|
||||
tmpl = env.from_string('{{ foo is sameas none }}')
|
||||
assert tmpl.render(foo=None) == 'True'
|
||||
|
||||
def test_escaped(self):
|
||||
env = Environment(autoescape=True)
|
||||
tmpl = env.from_string('{{ x is escaped }}|{{ y is escaped }}')
|
||||
assert tmpl.render(x='foo', y=Markup('foo')) == 'False|True'
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TestsTestCase))
|
||||
return suite
|
||||
Vendored
-82
@@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.testsuite.utils
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests utilities jinja uses.
|
||||
|
||||
:copyright: (c) 2010 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import gc
|
||||
import unittest
|
||||
|
||||
import pickle
|
||||
|
||||
from jinja2.testsuite import JinjaTestCase
|
||||
|
||||
from jinja2.utils import LRUCache, escape, object_type_repr
|
||||
|
||||
|
||||
class LRUCacheTestCase(JinjaTestCase):
|
||||
|
||||
def test_simple(self):
|
||||
d = LRUCache(3)
|
||||
d["a"] = 1
|
||||
d["b"] = 2
|
||||
d["c"] = 3
|
||||
d["a"]
|
||||
d["d"] = 4
|
||||
assert len(d) == 3
|
||||
assert 'a' in d and 'c' in d and 'd' in d and 'b' not in d
|
||||
|
||||
def test_pickleable(self):
|
||||
cache = LRUCache(2)
|
||||
cache["foo"] = 42
|
||||
cache["bar"] = 23
|
||||
cache["foo"]
|
||||
|
||||
for protocol in range(3):
|
||||
copy = pickle.loads(pickle.dumps(cache, protocol))
|
||||
assert copy.capacity == cache.capacity
|
||||
assert copy._mapping == cache._mapping
|
||||
assert copy._queue == cache._queue
|
||||
|
||||
|
||||
class HelpersTestCase(JinjaTestCase):
|
||||
|
||||
def test_object_type_repr(self):
|
||||
class X(object):
|
||||
pass
|
||||
self.assert_equal(object_type_repr(42), 'int object')
|
||||
self.assert_equal(object_type_repr([]), 'list object')
|
||||
self.assert_equal(object_type_repr(X()),
|
||||
'jinja2.testsuite.utils.X object')
|
||||
self.assert_equal(object_type_repr(None), 'None')
|
||||
self.assert_equal(object_type_repr(Ellipsis), 'Ellipsis')
|
||||
|
||||
|
||||
class MarkupLeakTestCase(JinjaTestCase):
|
||||
|
||||
def test_markup_leaks(self):
|
||||
counts = set()
|
||||
for count in range(20):
|
||||
for item in range(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(LRUCacheTestCase))
|
||||
suite.addTest(unittest.makeSuite(HelpersTestCase))
|
||||
|
||||
# this test only tests the c extension
|
||||
if not hasattr(escape, 'func_code'):
|
||||
suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
|
||||
|
||||
return suite
|
||||
Vendored
+22
-11
@@ -11,8 +11,9 @@
|
||||
import re
|
||||
import errno
|
||||
from collections import deque
|
||||
from threading import Lock
|
||||
from jinja2._compat import text_type, string_types, implements_iterator, \
|
||||
allocate_lock, url_quote
|
||||
url_quote
|
||||
|
||||
|
||||
_word_split_re = re.compile(r'(\s+)')
|
||||
@@ -149,7 +150,7 @@ def open_if_exists(filename, mode='rb'):
|
||||
try:
|
||||
return open(filename, mode)
|
||||
except IOError as e:
|
||||
if e.errno not in (errno.ENOENT, errno.EISDIR):
|
||||
if e.errno not in (errno.ENOENT, errno.EISDIR, errno.EINVAL):
|
||||
raise
|
||||
|
||||
|
||||
@@ -182,7 +183,7 @@ def pformat(obj, verbose=False):
|
||||
return pformat(obj)
|
||||
|
||||
|
||||
def urlize(text, trim_url_limit=None, nofollow=False):
|
||||
def urlize(text, trim_url_limit=None, nofollow=False, target=None):
|
||||
"""Converts any URLs in text into clickable links. Works on http://,
|
||||
https:// and www. links. Links can have trailing punctuation (periods,
|
||||
commas, close-parens) and leading punctuation (opening parens) and
|
||||
@@ -193,12 +194,18 @@ def urlize(text, trim_url_limit=None, nofollow=False):
|
||||
|
||||
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
||||
attribute.
|
||||
|
||||
If target is not None, a target attribute will be added to the link.
|
||||
"""
|
||||
trim_url = lambda x, limit=trim_url_limit: limit is not None \
|
||||
and (x[:limit] + (len(x) >=limit and '...'
|
||||
or '')) or x
|
||||
words = _word_split_re.split(text_type(escape(text)))
|
||||
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
||||
if target is not None and isinstance(target, string_types):
|
||||
target_attr = ' target="%s"' % target
|
||||
else:
|
||||
target_attr = ''
|
||||
for i, word in enumerate(words):
|
||||
match = _punctuation_re.match(word)
|
||||
if match:
|
||||
@@ -213,12 +220,12 @@ def urlize(text, trim_url_limit=None, nofollow=False):
|
||||
middle.endswith('.net') or
|
||||
middle.endswith('.com')
|
||||
)):
|
||||
middle = '<a href="http://%s"%s>%s</a>' % (middle,
|
||||
nofollow_attr, trim_url(middle))
|
||||
middle = '<a href="http://%s"%s%s>%s</a>' % (middle,
|
||||
nofollow_attr, target_attr, trim_url(middle))
|
||||
if middle.startswith('http://') or \
|
||||
middle.startswith('https://'):
|
||||
middle = '<a href="%s"%s>%s</a>' % (middle,
|
||||
nofollow_attr, trim_url(middle))
|
||||
middle = '<a href="%s"%s%s>%s</a>' % (middle,
|
||||
nofollow_attr, target_attr, trim_url(middle))
|
||||
if '@' in middle and not middle.startswith('www.') and \
|
||||
not ':' in middle and _simple_email_re.match(middle):
|
||||
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
|
||||
@@ -228,7 +235,7 @@ def urlize(text, trim_url_limit=None, nofollow=False):
|
||||
|
||||
|
||||
def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
|
||||
"""Generate some lorem impsum for the template."""
|
||||
"""Generate some lorem ipsum for the template."""
|
||||
from jinja2.constants import LOREM_IPSUM_WORDS
|
||||
from random import choice, randrange
|
||||
words = LOREM_IPSUM_WORDS.split()
|
||||
@@ -276,7 +283,7 @@ def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
|
||||
return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
|
||||
|
||||
|
||||
def unicode_urlencode(obj, charset='utf-8'):
|
||||
def unicode_urlencode(obj, charset='utf-8', for_qs=False):
|
||||
"""URL escapes a single bytestring or unicode string with the
|
||||
given charset if applicable to URL safe quoting under all rules
|
||||
that need to be considered under all supported Python versions.
|
||||
@@ -288,7 +295,11 @@ def unicode_urlencode(obj, charset='utf-8'):
|
||||
obj = text_type(obj)
|
||||
if isinstance(obj, text_type):
|
||||
obj = obj.encode(charset)
|
||||
return text_type(url_quote(obj))
|
||||
safe = for_qs and b'' or b'/'
|
||||
rv = text_type(url_quote(obj, safe))
|
||||
if for_qs:
|
||||
rv = rv.replace('%20', '+')
|
||||
return rv
|
||||
|
||||
|
||||
class LRUCache(object):
|
||||
@@ -309,7 +320,7 @@ class LRUCache(object):
|
||||
self._popleft = self._queue.popleft
|
||||
self._pop = self._queue.pop
|
||||
self._remove = self._queue.remove
|
||||
self._wlock = allocate_lock()
|
||||
self._wlock = Lock()
|
||||
self._append = self._queue.append
|
||||
|
||||
def __getstate__(self):
|
||||
|
||||
Reference in New Issue
Block a user