mirror of https://github.com/opencv/opencv.git
parent
224f44a255
commit
86b7e3d15d
34 changed files with 1615 additions and 63 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,32 @@ |
||||
# -*- coding: utf-8 -*- |
||||
""" |
||||
jinja.constants |
||||
~~~~~~~~~~~~~~~ |
||||
|
||||
Various constants. |
||||
|
||||
:copyright: (c) 2010 by the Jinja Team. |
||||
:license: BSD, see LICENSE for more details. |
||||
""" |
||||
|
||||
|
||||
#: list of lorem ipsum words used by the lipsum() helper function |
||||
LOREM_IPSUM_WORDS = u'''\ |
||||
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at |
||||
auctor augue bibendum blandit class commodo condimentum congue consectetuer |
||||
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus |
||||
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend |
||||
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames |
||||
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac |
||||
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum |
||||
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem |
||||
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie |
||||
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non |
||||
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque |
||||
penatibus per pharetra phasellus placerat platea porta porttitor posuere |
||||
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus |
||||
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit |
||||
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor |
||||
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices |
||||
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus |
||||
viverra volutpat vulputate''' |
@ -0,0 +1,636 @@ |
||||
# -*- coding: utf-8 -*- |
||||
""" |
||||
jinja2.ext |
||||
~~~~~~~~~~ |
||||
|
||||
Jinja extensions allow to add custom tags similar to the way django custom |
||||
tags work. By default two example extensions exist: an i18n and a cache |
||||
extension. |
||||
|
||||
:copyright: (c) 2010 by the Jinja Team. |
||||
:license: BSD. |
||||
""" |
||||
from jinja2 import nodes |
||||
from jinja2.defaults import BLOCK_START_STRING, \ |
||||
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ |
||||
COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \ |
||||
LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \ |
||||
KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS |
||||
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 |
||||
|
||||
|
||||
# the only real useful gettext functions for a Jinja template. Note |
||||
# that ugettext must be assigned to gettext as Jinja doesn't support |
||||
# non unicode strings. |
||||
GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') |
||||
|
||||
|
||||
class ExtensionRegistry(type): |
||||
"""Gives the extension an unique identifier.""" |
||||
|
||||
def __new__(cls, name, bases, d): |
||||
rv = type.__new__(cls, name, bases, d) |
||||
rv.identifier = rv.__module__ + '.' + rv.__name__ |
||||
return rv |
||||
|
||||
|
||||
class Extension(with_metaclass(ExtensionRegistry, object)): |
||||
"""Extensions can be used to add extra functionality to the Jinja template |
||||
system at the parser level. Custom extensions are bound to an environment |
||||
but may not store environment specific data on `self`. The reason for |
||||
this is that an extension can be bound to another environment (for |
||||
overlays) by creating a copy and reassigning the `environment` attribute. |
||||
|
||||
As extensions are created by the environment they cannot accept any |
||||
arguments for configuration. One may want to work around that by using |
||||
a factory function, but that is not possible as extensions are identified |
||||
by their import name. The correct way to configure the extension is |
||||
storing the configuration values on the environment. Because this way the |
||||
environment ends up acting as central configuration storage the |
||||
attributes may clash which is why extensions have to ensure that the names |
||||
they choose for configuration are not too generic. ``prefix`` for example |
||||
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good |
||||
name as includes the name of the extension (fragment cache). |
||||
""" |
||||
|
||||
#: if this extension parses this is the list of tags it's listening to. |
||||
tags = set() |
||||
|
||||
#: the priority of that extension. This is especially useful for |
||||
#: extensions that preprocess values. A lower value means higher |
||||
#: priority. |
||||
#: |
||||
#: .. versionadded:: 2.4 |
||||
priority = 100 |
||||
|
||||
def __init__(self, environment): |
||||
self.environment = environment |
||||
|
||||
def bind(self, environment): |
||||
"""Create a copy of this extension bound to another environment.""" |
||||
rv = object.__new__(self.__class__) |
||||
rv.__dict__.update(self.__dict__) |
||||
rv.environment = environment |
||||
return rv |
||||
|
||||
def preprocess(self, source, name, filename=None): |
||||
"""This method is called before the actual lexing and can be used to |
||||
preprocess the source. The `filename` is optional. The return value |
||||
must be the preprocessed source. |
||||
""" |
||||
return source |
||||
|
||||
def filter_stream(self, stream): |
||||
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used |
||||
to filter tokens returned. This method has to return an iterable of |
||||
:class:`~jinja2.lexer.Token`\s, but it doesn't have to return a |
||||
:class:`~jinja2.lexer.TokenStream`. |
||||
|
||||
In the `ext` folder of the Jinja2 source distribution there is a file |
||||
called `inlinegettext.py` which implements a filter that utilizes this |
||||
method. |
||||
""" |
||||
return stream |
||||
|
||||
def parse(self, parser): |
||||
"""If any of the :attr:`tags` matched this method is called with the |
||||
parser as first argument. The token the parser stream is pointing at |
||||
is the name token that matched. This method has to return one or a |
||||
list of multiple nodes. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
def attr(self, name, lineno=None): |
||||
"""Return an attribute node for the current extension. This is useful |
||||
to pass constants on extensions to generated template code. |
||||
|
||||
:: |
||||
|
||||
self.attr('_my_attribute', lineno=lineno) |
||||
""" |
||||
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) |
||||
|
||||
def call_method(self, name, args=None, kwargs=None, dyn_args=None, |
||||
dyn_kwargs=None, lineno=None): |
||||
"""Call a method of the extension. This is a shortcut for |
||||
:meth:`attr` + :class:`jinja2.nodes.Call`. |
||||
""" |
||||
if args is None: |
||||
args = [] |
||||
if kwargs is None: |
||||
kwargs = [] |
||||
return nodes.Call(self.attr(name, lineno=lineno), args, kwargs, |
||||
dyn_args, dyn_kwargs, lineno=lineno) |
||||
|
||||
|
||||
@contextfunction |
||||
def _gettext_alias(__context, *args, **kwargs): |
||||
return __context.call(__context.resolve('gettext'), *args, **kwargs) |
||||
|
||||
|
||||
def _make_new_gettext(func): |
||||
@contextfunction |
||||
def gettext(__context, __string, **variables): |
||||
rv = __context.call(func, __string) |
||||
if __context.eval_ctx.autoescape: |
||||
rv = Markup(rv) |
||||
return rv % variables |
||||
return gettext |
||||
|
||||
|
||||
def _make_new_ngettext(func): |
||||
@contextfunction |
||||
def ngettext(__context, __singular, __plural, __num, **variables): |
||||
variables.setdefault('num', __num) |
||||
rv = __context.call(func, __singular, __plural, __num) |
||||
if __context.eval_ctx.autoescape: |
||||
rv = Markup(rv) |
||||
return rv % variables |
||||
return ngettext |
||||
|
||||
|
||||
class InternationalizationExtension(Extension): |
||||
"""This extension adds gettext support to Jinja2.""" |
||||
tags = set(['trans']) |
||||
|
||||
# TODO: the i18n extension is currently reevaluating values in a few |
||||
# situations. Take this example: |
||||
# {% trans count=something() %}{{ count }} foo{% pluralize |
||||
# %}{{ count }} fooss{% endtrans %} |
||||
# something is called twice here. One time for the gettext value and |
||||
# the other time for the n-parameter of the ngettext function. |
||||
|
||||
def __init__(self, environment): |
||||
Extension.__init__(self, environment) |
||||
environment.globals['_'] = _gettext_alias |
||||
environment.extend( |
||||
install_gettext_translations=self._install, |
||||
install_null_translations=self._install_null, |
||||
install_gettext_callables=self._install_callables, |
||||
uninstall_gettext_translations=self._uninstall, |
||||
extract_translations=self._extract, |
||||
newstyle_gettext=False |
||||
) |
||||
|
||||
def _install(self, translations, newstyle=None): |
||||
gettext = getattr(translations, 'ugettext', None) |
||||
if gettext is None: |
||||
gettext = translations.gettext |
||||
ngettext = getattr(translations, 'ungettext', None) |
||||
if ngettext is None: |
||||
ngettext = translations.ngettext |
||||
self._install_callables(gettext, ngettext, newstyle) |
||||
|
||||
def _install_null(self, newstyle=None): |
||||
self._install_callables( |
||||
lambda x: x, |
||||
lambda s, p, n: (n != 1 and (p,) or (s,))[0], |
||||
newstyle |
||||
) |
||||
|
||||
def _install_callables(self, gettext, ngettext, newstyle=None): |
||||
if newstyle is not None: |
||||
self.environment.newstyle_gettext = newstyle |
||||
if self.environment.newstyle_gettext: |
||||
gettext = _make_new_gettext(gettext) |
||||
ngettext = _make_new_ngettext(ngettext) |
||||
self.environment.globals.update( |
||||
gettext=gettext, |
||||
ngettext=ngettext |
||||
) |
||||
|
||||
def _uninstall(self, translations): |
||||
for key in 'gettext', 'ngettext': |
||||
self.environment.globals.pop(key, None) |
||||
|
||||
def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): |
||||
if isinstance(source, string_types): |
||||
source = self.environment.parse(source) |
||||
return extract_from_ast(source, gettext_functions) |
||||
|
||||
def parse(self, parser): |
||||
"""Parse a translatable tag.""" |
||||
lineno = next(parser.stream).lineno |
||||
num_called_num = False |
||||
|
||||
# find all the variables referenced. Additionally a variable can be |
||||
# defined in the body of the trans block too, but this is checked at |
||||
# a later state. |
||||
plural_expr = None |
||||
plural_expr_assignment = None |
||||
variables = {} |
||||
while parser.stream.current.type != 'block_end': |
||||
if variables: |
||||
parser.stream.expect('comma') |
||||
|
||||
# skip colon for python compatibility |
||||
if parser.stream.skip_if('colon'): |
||||
break |
||||
|
||||
name = parser.stream.expect('name') |
||||
if name.value in variables: |
||||
parser.fail('translatable variable %r defined twice.' % |
||||
name.value, name.lineno, |
||||
exc=TemplateAssertionError) |
||||
|
||||
# expressions |
||||
if parser.stream.current.type == 'assign': |
||||
next(parser.stream) |
||||
variables[name.value] = var = parser.parse_expression() |
||||
else: |
||||
variables[name.value] = var = nodes.Name(name.value, 'load') |
||||
|
||||
if plural_expr is None: |
||||
if isinstance(var, nodes.Call): |
||||
plural_expr = nodes.Name('_trans', 'load') |
||||
variables[name.value] = plural_expr |
||||
plural_expr_assignment = nodes.Assign( |
||||
nodes.Name('_trans', 'store'), var) |
||||
else: |
||||
plural_expr = var |
||||
num_called_num = name.value == 'num' |
||||
|
||||
parser.stream.expect('block_end') |
||||
|
||||
plural = plural_names = None |
||||
have_plural = False |
||||
referenced = set() |
||||
|
||||
# now parse until endtrans or pluralize |
||||
singular_names, singular = self._parse_block(parser, True) |
||||
if singular_names: |
||||
referenced.update(singular_names) |
||||
if plural_expr is None: |
||||
plural_expr = nodes.Name(singular_names[0], 'load') |
||||
num_called_num = singular_names[0] == 'num' |
||||
|
||||
# if we have a pluralize block, we parse that too |
||||
if parser.stream.current.test('name:pluralize'): |
||||
have_plural = True |
||||
next(parser.stream) |
||||
if parser.stream.current.type != 'block_end': |
||||
name = parser.stream.expect('name') |
||||
if name.value not in variables: |
||||
parser.fail('unknown variable %r for pluralization' % |
||||
name.value, name.lineno, |
||||
exc=TemplateAssertionError) |
||||
plural_expr = variables[name.value] |
||||
num_called_num = name.value == 'num' |
||||
parser.stream.expect('block_end') |
||||
plural_names, plural = self._parse_block(parser, False) |
||||
next(parser.stream) |
||||
referenced.update(plural_names) |
||||
else: |
||||
next(parser.stream) |
||||
|
||||
# register free names as simple name expressions |
||||
for var in referenced: |
||||
if var not in variables: |
||||
variables[var] = nodes.Name(var, 'load') |
||||
|
||||
if not have_plural: |
||||
plural_expr = None |
||||
elif plural_expr is None: |
||||
parser.fail('pluralize without variables', lineno) |
||||
|
||||
node = self._make_node(singular, plural, variables, plural_expr, |
||||
bool(referenced), |
||||
num_called_num and have_plural) |
||||
node.set_lineno(lineno) |
||||
if plural_expr_assignment is not None: |
||||
return [plural_expr_assignment, node] |
||||
else: |
||||
return node |
||||
|
||||
def _parse_block(self, parser, allow_pluralize): |
||||
"""Parse until the next block tag with a given name.""" |
||||
referenced = [] |
||||
buf = [] |
||||
while 1: |
||||
if parser.stream.current.type == 'data': |
||||
buf.append(parser.stream.current.value.replace('%', '%%')) |
||||
next(parser.stream) |
||||
elif parser.stream.current.type == 'variable_begin': |
||||
next(parser.stream) |
||||
name = parser.stream.expect('name').value |
||||
referenced.append(name) |
||||
buf.append('%%(%s)s' % name) |
||||
parser.stream.expect('variable_end') |
||||
elif parser.stream.current.type == 'block_begin': |
||||
next(parser.stream) |
||||
if parser.stream.current.test('name:endtrans'): |
||||
break |
||||
elif parser.stream.current.test('name:pluralize'): |
||||
if allow_pluralize: |
||||
break |
||||
parser.fail('a translatable section can have only one ' |
||||
'pluralize section') |
||||
parser.fail('control structures in translatable sections are ' |
||||
'not allowed') |
||||
elif parser.stream.eos: |
||||
parser.fail('unclosed translation block') |
||||
else: |
||||
assert False, 'internal parser error' |
||||
|
||||
return referenced, concat(buf) |
||||
|
||||
def _make_node(self, singular, plural, variables, plural_expr, |
||||
vars_referenced, num_called_num): |
||||
"""Generates a useful node from the data provided.""" |
||||
# no variables referenced? no need to escape for old style |
||||
# gettext invocations only if there are vars. |
||||
if not vars_referenced and not self.environment.newstyle_gettext: |
||||
singular = singular.replace('%%', '%') |
||||
if plural: |
||||
plural = plural.replace('%%', '%') |
||||
|
||||
# singular only: |
||||
if plural_expr is None: |
||||
gettext = nodes.Name('gettext', 'load') |
||||
node = nodes.Call(gettext, [nodes.Const(singular)], |
||||
[], None, None) |
||||
|
||||
# singular and plural |
||||
else: |
||||
ngettext = nodes.Name('ngettext', 'load') |
||||
node = nodes.Call(ngettext, [ |
||||
nodes.Const(singular), |
||||
nodes.Const(plural), |
||||
plural_expr |
||||
], [], None, None) |
||||
|
||||
# in case newstyle gettext is used, the method is powerful |
||||
# enough to handle the variable expansion and autoescape |
||||
# handling itself |
||||
if self.environment.newstyle_gettext: |
||||
for key, value in iteritems(variables): |
||||
# the function adds that later anyways in case num was |
||||
# called num, so just skip it. |
||||
if num_called_num and key == 'num': |
||||
continue |
||||
node.kwargs.append(nodes.Keyword(key, value)) |
||||
|
||||
# otherwise do that here |
||||
else: |
||||
# mark the return value as safe if we are in an |
||||
# environment with autoescaping turned on |
||||
node = nodes.MarkSafeIfAutoescape(node) |
||||
if variables: |
||||
node = nodes.Mod(node, nodes.Dict([ |
||||
nodes.Pair(nodes.Const(key), value) |
||||
for key, value in variables.items() |
||||
])) |
||||
return nodes.Output([node]) |
||||
|
||||
|
||||
class ExprStmtExtension(Extension): |
||||
"""Adds a `do` tag to Jinja2 that works like the print statement just |
||||
that it doesn't print the return value. |
||||
""" |
||||
tags = set(['do']) |
||||
|
||||
def parse(self, parser): |
||||
node = nodes.ExprStmt(lineno=next(parser.stream).lineno) |
||||
node.node = parser.parse_tuple() |
||||
return node |
||||
|
||||
|
||||
class LoopControlExtension(Extension): |
||||
"""Adds break and continue to the template engine.""" |
||||
tags = set(['break', 'continue']) |
||||
|
||||
def parse(self, parser): |
||||
token = next(parser.stream) |
||||
if token.value == 'break': |
||||
return nodes.Break(lineno=token.lineno) |
||||
return nodes.Continue(lineno=token.lineno) |
||||
|
||||
|
||||
class WithExtension(Extension): |
||||
"""Adds support for a django-like with block.""" |
||||
tags = set(['with']) |
||||
|
||||
def parse(self, parser): |
||||
node = nodes.Scope(lineno=next(parser.stream).lineno) |
||||
assignments = [] |
||||
while parser.stream.current.type != 'block_end': |
||||
lineno = parser.stream.current.lineno |
||||
if assignments: |
||||
parser.stream.expect('comma') |
||||
target = parser.parse_assign_target() |
||||
parser.stream.expect('assign') |
||||
expr = parser.parse_expression() |
||||
assignments.append(nodes.Assign(target, expr, lineno=lineno)) |
||||
node.body = assignments + \ |
||||
list(parser.parse_statements(('name:endwith',), |
||||
drop_needle=True)) |
||||
return node |
||||
|
||||
|
||||
class AutoEscapeExtension(Extension): |
||||
"""Changes auto escape rules for a scope.""" |
||||
tags = set(['autoescape']) |
||||
|
||||
def parse(self, parser): |
||||
node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno) |
||||
node.options = [ |
||||
nodes.Keyword('autoescape', parser.parse_expression()) |
||||
] |
||||
node.body = parser.parse_statements(('name:endautoescape',), |
||||
drop_needle=True) |
||||
return nodes.Scope([node]) |
||||
|
||||
|
||||
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, |
||||
babel_style=True): |
||||
"""Extract localizable strings from the given template node. Per |
||||
default this function returns matches in babel style that means non string |
||||
parameters as well as keyword arguments are returned as `None`. This |
||||
allows Babel to figure out what you really meant if you are using |
||||
gettext functions that allow keyword arguments for placeholder expansion. |
||||
If you don't want that behavior set the `babel_style` parameter to `False` |
||||
which causes only strings to be returned and parameters are always stored |
||||
in tuples. As a consequence invalid gettext calls (calls without a single |
||||
string parameter or string parameters after non-string parameters) are |
||||
skipped. |
||||
|
||||
This example explains the behavior: |
||||
|
||||
>>> from jinja2 import Environment |
||||
>>> env = Environment() |
||||
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') |
||||
>>> list(extract_from_ast(node)) |
||||
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] |
||||
>>> list(extract_from_ast(node, babel_style=False)) |
||||
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] |
||||
|
||||
For every string found this function yields a ``(lineno, function, |
||||
message)`` tuple, where: |
||||
|
||||
* ``lineno`` is the number of the line on which the string was found, |
||||
* ``function`` is the name of the ``gettext`` function used (if the |
||||
string was extracted from embedded Python code), and |
||||
* ``message`` is the string itself (a ``unicode`` object, or a tuple |
||||
of ``unicode`` objects for functions with multiple string arguments). |
||||
|
||||
This extraction function operates on the AST and is because of that unable |
||||
to extract any comments. For comment support you have to use the babel |
||||
extraction interface or extract comments yourself. |
||||
""" |
||||
for node in node.find_all(nodes.Call): |
||||
if not isinstance(node.node, nodes.Name) or \ |
||||
node.node.name not in gettext_functions: |
||||
continue |
||||
|
||||
strings = [] |
||||
for arg in node.args: |
||||
if isinstance(arg, nodes.Const) and \ |
||||
isinstance(arg.value, string_types): |
||||
strings.append(arg.value) |
||||
else: |
||||
strings.append(None) |
||||
|
||||
for arg in node.kwargs: |
||||
strings.append(None) |
||||
if node.dyn_args is not None: |
||||
strings.append(None) |
||||
if node.dyn_kwargs is not None: |
||||
strings.append(None) |
||||
|
||||
if not babel_style: |
||||
strings = tuple(x for x in strings if x is not None) |
||||
if not strings: |
||||
continue |
||||
else: |
||||
if len(strings) == 1: |
||||
strings = strings[0] |
||||
else: |
||||
strings = tuple(strings) |
||||
yield node.lineno, node.node.name, strings |
||||
|
||||
|
||||
class _CommentFinder(object): |
||||
"""Helper class to find comments in a token stream. Can only |
||||
find comments for gettext calls forwards. Once the comment |
||||
from line 4 is found, a comment for line 1 will not return a |
||||
usable value. |
||||
""" |
||||
|
||||
def __init__(self, tokens, comment_tags): |
||||
self.tokens = tokens |
||||
self.comment_tags = comment_tags |
||||
self.offset = 0 |
||||
self.last_lineno = 0 |
||||
|
||||
def find_backwards(self, offset): |
||||
try: |
||||
for _, token_type, token_value in \ |
||||
reversed(self.tokens[self.offset:offset]): |
||||
if token_type in ('comment', 'linecomment'): |
||||
try: |
||||
prefix, comment = token_value.split(None, 1) |
||||
except ValueError: |
||||
continue |
||||
if prefix in self.comment_tags: |
||||
return [comment.rstrip()] |
||||
return [] |
||||
finally: |
||||
self.offset = offset |
||||
|
||||
def find_comments(self, lineno): |
||||
if not self.comment_tags or self.last_lineno > lineno: |
||||
return [] |
||||
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]): |
||||
if token_lineno > lineno: |
||||
return self.find_backwards(self.offset + idx) |
||||
return self.find_backwards(len(self.tokens)) |
||||
|
||||
|
||||
def babel_extract(fileobj, keywords, comment_tags, options): |
||||
"""Babel extraction method for Jinja templates. |
||||
|
||||
.. versionchanged:: 2.3 |
||||
Basic support for translation comments was added. If `comment_tags` |
||||
is now set to a list of keywords for extraction, the extractor will |
||||
try to find the best preceeding comment that begins with one of the |
||||
keywords. For best results, make sure to not have more than one |
||||
gettext call in one line of code and the matching comment in the |
||||
same line or the line before. |
||||
|
||||
.. versionchanged:: 2.5.1 |
||||
The `newstyle_gettext` flag can be set to `True` to enable newstyle |
||||
gettext calls. |
||||
|
||||
.. versionchanged:: 2.7 |
||||
A `silent` option can now be provided. If set to `False` template |
||||
syntax errors are propagated instead of being ignored. |
||||
|
||||
:param fileobj: the file-like object the messages should be extracted from |
||||
:param keywords: a list of keywords (i.e. function names) that should be |
||||
recognized as translation functions |
||||
:param comment_tags: a list of translator tags to search for and include |
||||
in the results. |
||||
:param options: a dictionary of additional options (optional) |
||||
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples. |
||||
(comments will be empty currently) |
||||
""" |
||||
extensions = set() |
||||
for extension in options.get('extensions', '').split(','): |
||||
extension = extension.strip() |
||||
if not extension: |
||||
continue |
||||
extensions.add(import_string(extension)) |
||||
if InternationalizationExtension not in extensions: |
||||
extensions.add(InternationalizationExtension) |
||||
|
||||
def getbool(options, key, default=False): |
||||
return options.get(key, str(default)).lower() in \ |
||||
('1', 'on', 'yes', 'true') |
||||
|
||||
silent = getbool(options, 'silent', True) |
||||
environment = Environment( |
||||
options.get('block_start_string', BLOCK_START_STRING), |
||||
options.get('block_end_string', BLOCK_END_STRING), |
||||
options.get('variable_start_string', VARIABLE_START_STRING), |
||||
options.get('variable_end_string', VARIABLE_END_STRING), |
||||
options.get('comment_start_string', COMMENT_START_STRING), |
||||
options.get('comment_end_string', COMMENT_END_STRING), |
||||
options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, |
||||
options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, |
||||
getbool(options, 'trim_blocks', TRIM_BLOCKS), |
||||
getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS), |
||||
NEWLINE_SEQUENCE, |
||||
getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE), |
||||
frozenset(extensions), |
||||
cache_size=0, |
||||
auto_reload=False |
||||
) |
||||
|
||||
if getbool(options, 'newstyle_gettext'): |
||||
environment.newstyle_gettext = True |
||||
|
||||
source = fileobj.read().decode(options.get('encoding', 'utf-8')) |
||||
try: |
||||
node = environment.parse(source) |
||||
tokens = list(environment.lex(environment.preprocess(source))) |
||||
except TemplateSyntaxError as e: |
||||
if not silent: |
||||
raise |
||||
# skip templates with syntax errors |
||||
return |
||||
|
||||
finder = _CommentFinder(tokens, comment_tags) |
||||
for lineno, func, message in extract_from_ast(node, keywords): |
||||
yield lineno, func, message, finder.find_comments(lineno) |
||||
|
||||
|
||||
#: nicer import names |
||||
i18n = InternationalizationExtension |
||||
do = ExprStmtExtension |
||||
loopcontrols = LoopControlExtension |
||||
with_ = WithExtension |
||||
autoescape = AutoEscapeExtension |
@ -0,0 +1,103 @@ |
||||
# -*- coding: utf-8 -*- |
||||
""" |
||||
jinja2.meta |
||||
~~~~~~~~~~~ |
||||
|
||||
This module implements various functions that exposes information about |
||||
templates that might be interesting for various kinds of applications. |
||||
|
||||
:copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details. |
||||
:license: BSD, see LICENSE for more details. |
||||
""" |
||||
from jinja2 import nodes |
||||
from jinja2.compiler import CodeGenerator |
||||
from jinja2._compat import string_types |
||||
|
||||
|
||||
class TrackingCodeGenerator(CodeGenerator): |
||||
"""We abuse the code generator for introspection.""" |
||||
|
||||
def __init__(self, environment): |
||||
CodeGenerator.__init__(self, environment, '<introspection>', |
||||
'<introspection>') |
||||
self.undeclared_identifiers = set() |
||||
|
||||
def write(self, x): |
||||
"""Don't write.""" |
||||
|
||||
def pull_locals(self, frame): |
||||
"""Remember all undeclared identifiers.""" |
||||
self.undeclared_identifiers.update(frame.identifiers.undeclared) |
||||
|
||||
|
||||
def find_undeclared_variables(ast): |
||||
"""Returns a set of all variables in the AST that will be looked up from |
||||
the context at runtime. Because at compile time it's not known which |
||||
variables will be used depending on the path the execution takes at |
||||
runtime, all variables are returned. |
||||
|
||||
>>> from jinja2 import Environment, meta |
||||
>>> env = Environment() |
||||
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}') |
||||
>>> meta.find_undeclared_variables(ast) |
||||
set(['bar']) |
||||
|
||||
.. admonition:: Implementation |
||||
|
||||
Internally the code generator is used for finding undeclared variables. |
||||
This is good to know because the code generator might raise a |
||||
:exc:`TemplateAssertionError` during compilation and as a matter of |
||||
fact this function can currently raise that exception as well. |
||||
""" |
||||
codegen = TrackingCodeGenerator(ast.environment) |
||||
codegen.visit(ast) |
||||
return codegen.undeclared_identifiers |
||||
|
||||
|
||||
def find_referenced_templates(ast): |
||||
"""Finds all the referenced templates from the AST. This will return an |
||||
iterator over all the hardcoded template extensions, inclusions and |
||||
imports. If dynamic inheritance or inclusion is used, `None` will be |
||||
yielded. |
||||
|
||||
>>> from jinja2 import Environment, meta |
||||
>>> env = Environment() |
||||
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}') |
||||
>>> list(meta.find_referenced_templates(ast)) |
||||
['layout.html', None] |
||||
|
||||
This function is useful for dependency tracking. For example if you want |
||||
to rebuild parts of the website after a layout template has changed. |
||||
""" |
||||
for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import, |
||||
nodes.Include)): |
||||
if not isinstance(node.template, nodes.Const): |
||||
# a tuple with some non consts in there |
||||
if isinstance(node.template, (nodes.Tuple, nodes.List)): |
||||
for template_name in node.template.items: |
||||
# something const, only yield the strings and ignore |
||||
# non-string consts that really just make no sense |
||||
if isinstance(template_name, nodes.Const): |
||||
if isinstance(template_name.value, string_types): |
||||
yield template_name.value |
||||
# something dynamic in there |
||||
else: |
||||
yield None |
||||
# something dynamic we don't know about here |
||||
else: |
||||
yield None |
||||
continue |
||||
# constant is a basestring, direct template name |
||||
if isinstance(node.template.value, string_types): |
||||
yield node.template.value |
||||
# a tuple or list (latter *should* not happen) made of consts, |
||||
# yield the consts that are strings. We could warn here for |
||||
# non string values |
||||
elif isinstance(node, nodes.Include) and \ |
||||
isinstance(node.template.value, (tuple, list)): |
||||
for template_name in node.template.value: |
||||
if isinstance(template_name, string_types): |
||||
yield template_name |
||||
# something else we don't care about, we could warn here |
||||
else: |
||||
yield None |
@ -0,0 +1,368 @@ |
||||
# -*- coding: utf-8 -*- |
||||
""" |
||||
jinja2.sandbox |
||||
~~~~~~~~~~~~~~ |
||||
|
||||
Adds a sandbox layer to Jinja as it was the default behavior in the old |
||||
Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the |
||||
default behavior is easier to use. |
||||
|
||||
The behavior can be changed by subclassing the environment. |
||||
|
||||
:copyright: (c) 2010 by the Jinja Team. |
||||
:license: BSD. |
||||
""" |
||||
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 |
||||
|
||||
|
||||
#: 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']) |
||||
|
||||
#: unsafe method attributes. function attributes are unsafe for methods too |
||||
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 |
||||
warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning, |
||||
module='jinja2.sandbox') |
||||
|
||||
from collections import deque |
||||
|
||||
_mutable_set_types = (set,) |
||||
_mutable_mapping_types = (dict,) |
||||
_mutable_sequence_types = (list,) |
||||
|
||||
|
||||
# on python 2.x we can register the user collection types |
||||
try: |
||||
from UserDict import UserDict, DictMixin |
||||
from UserList import UserList |
||||
_mutable_mapping_types += (UserDict, DictMixin) |
||||
_mutable_set_types += (UserList,) |
||||
except ImportError: |
||||
pass |
||||
|
||||
# if sets is still available, register the mutable set from there as well |
||||
try: |
||||
from sets import Set |
||||
_mutable_set_types += (Set,) |
||||
except ImportError: |
||||
pass |
||||
|
||||
#: register Python 2.6 abstract base classes |
||||
try: |
||||
from collections import MutableSet, MutableMapping, MutableSequence |
||||
_mutable_set_types += (MutableSet,) |
||||
_mutable_mapping_types += (MutableMapping,) |
||||
_mutable_sequence_types += (MutableSequence,) |
||||
except ImportError: |
||||
pass |
||||
|
||||
_mutable_spec = ( |
||||
(_mutable_set_types, frozenset([ |
||||
'add', 'clear', 'difference_update', 'discard', 'pop', 'remove', |
||||
'symmetric_difference_update', 'update' |
||||
])), |
||||
(_mutable_mapping_types, frozenset([ |
||||
'clear', 'pop', 'popitem', 'setdefault', 'update' |
||||
])), |
||||
(_mutable_sequence_types, frozenset([ |
||||
'append', 'reverse', 'insert', 'sort', 'extend', 'remove' |
||||
])), |
||||
(deque, frozenset([ |
||||
'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop', |
||||
'popleft', 'remove', 'rotate' |
||||
])) |
||||
) |
||||
|
||||
|
||||
def safe_range(*args): |
||||
"""A range that can't generate ranges with a length of more than |
||||
MAX_RANGE items. |
||||
""" |
||||
rng = range(*args) |
||||
if len(rng) > MAX_RANGE: |
||||
raise OverflowError('range too big, maximum size for range is %d' % |
||||
MAX_RANGE) |
||||
return rng |
||||
|
||||
|
||||
def unsafe(f): |
||||
"""Marks a function or method as unsafe. |
||||
|
||||
:: |
||||
|
||||
@unsafe |
||||
def delete(self): |
||||
pass |
||||
""" |
||||
f.unsafe_callable = True |
||||
return f |
||||
|
||||
|
||||
def is_internal_attribute(obj, attr): |
||||
"""Test if the attribute given is an internal python attribute. For |
||||
example this function returns `True` for the `func_code` attribute of |
||||
python objects. This is useful if the environment method |
||||
: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') |
||||
True |
||||
>>> is_internal_attribute(str, "upper") |
||||
False |
||||
""" |
||||
if isinstance(obj, function_type): |
||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES: |
||||
return True |
||||
elif isinstance(obj, method_type): |
||||
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)): |
||||
return True |
||||
elif isinstance(obj, generator_type): |
||||
if attr in UNSAFE_GENERATOR_ATTRIBUTES: |
||||
return True |
||||
return attr.startswith('__') |
||||
|
||||
|
||||
def modifies_known_mutable(obj, attr): |
||||
"""This function checks if an attribute on a builtin mutable object |
||||
(list, dict, set or deque) would modify it if called. It also supports |
||||
the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and |
||||
with Python 2.6 onwards the abstract base classes `MutableSet`, |
||||
`MutableMapping`, and `MutableSequence`. |
||||
|
||||
>>> modifies_known_mutable({}, "clear") |
||||
True |
||||
>>> modifies_known_mutable({}, "keys") |
||||
False |
||||
>>> modifies_known_mutable([], "append") |
||||
True |
||||
>>> modifies_known_mutable([], "index") |
||||
False |
||||
|
||||
If called with an unsupported object (such as unicode) `False` is |
||||
returned. |
||||
|
||||
>>> modifies_known_mutable("foo", "upper") |
||||
False |
||||
""" |
||||
for typespec, unsafe in _mutable_spec: |
||||
if isinstance(obj, typespec): |
||||
return attr in unsafe |
||||
return False |
||||
|
||||
|
||||
class SandboxedEnvironment(Environment): |
||||
"""The sandboxed environment. It works like the regular environment but |
||||
tells the compiler to generate sandboxed code. Additionally subclasses of |
||||
this environment may override the methods that tell the runtime what |
||||
attributes or functions are safe to access. |
||||
|
||||
If the template tries to access insecure code a :exc:`SecurityError` is |
||||
raised. However also other exceptions may occour during the rendering so |
||||
the caller has to ensure that all exceptions are catched. |
||||
""" |
||||
sandboxed = True |
||||
|
||||
#: default callback table for the binary operators. A copy of this is |
||||
#: available on each instance of a sandboxed environment as |
||||
#: :attr:`binop_table` |
||||
default_binop_table = { |
||||
'+': operator.add, |
||||
'-': operator.sub, |
||||
'*': operator.mul, |
||||
'/': operator.truediv, |
||||
'//': operator.floordiv, |
||||
'**': operator.pow, |
||||
'%': operator.mod |
||||
} |
||||
|
||||
#: default callback table for the unary operators. A copy of this is |
||||
#: available on each instance of a sandboxed environment as |
||||
#: :attr:`unop_table` |
||||
default_unop_table = { |
||||
'+': operator.pos, |
||||
'-': operator.neg |
||||
} |
||||
|
||||
#: a set of binary operators that should be intercepted. Each operator |
||||
#: that is added to this set (empty by default) is delegated to the |
||||
#: :meth:`call_binop` method that will perform the operator. The default |
||||
#: operator callback is specified by :attr:`binop_table`. |
||||
#: |
||||
#: The following binary operators are interceptable: |
||||
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**`` |
||||
#: |
||||
#: The default operation form the operator table corresponds to the |
||||
#: builtin function. Intercepted calls are always slower than the native |
||||
#: operator call, so make sure only to intercept the ones you are |
||||
#: interested in. |
||||
#: |
||||
#: .. versionadded:: 2.6 |
||||
intercepted_binops = frozenset() |
||||
|
||||
#: a set of unary operators that should be intercepted. Each operator |
||||
#: that is added to this set (empty by default) is delegated to the |
||||
#: :meth:`call_unop` method that will perform the operator. The default |
||||
#: operator callback is specified by :attr:`unop_table`. |
||||
#: |
||||
#: The following unary operators are interceptable: ``+``, ``-`` |
||||
#: |
||||
#: The default operation form the operator table corresponds to the |
||||
#: builtin function. Intercepted calls are always slower than the native |
||||
#: operator call, so make sure only to intercept the ones you are |
||||
#: interested in. |
||||
#: |
||||
#: .. versionadded:: 2.6 |
||||
intercepted_unops = frozenset() |
||||
|
||||
def intercept_unop(self, operator): |
||||
"""Called during template compilation with the name of a unary |
||||
operator to check if it should be intercepted at runtime. If this |
||||
method returns `True`, :meth:`call_unop` is excuted for this unary |
||||
operator. The default implementation of :meth:`call_unop` will use |
||||
the :attr:`unop_table` dictionary to perform the operator with the |
||||
same logic as the builtin one. |
||||
|
||||
The following unary operators are interceptable: ``+`` and ``-`` |
||||
|
||||
Intercepted calls are always slower than the native operator call, |
||||
so make sure only to intercept the ones you are interested in. |
||||
|
||||
.. versionadded:: 2.6 |
||||
""" |
||||
return False |
||||
|
||||
|
||||
def __init__(self, *args, **kwargs): |
||||
Environment.__init__(self, *args, **kwargs) |
||||
self.globals['range'] = safe_range |
||||
self.binop_table = self.default_binop_table.copy() |
||||
self.unop_table = self.default_unop_table.copy() |
||||
|
||||
def is_safe_attribute(self, obj, attr, value): |
||||
"""The sandboxed environment will call this method to check if the |
||||
attribute of an object is safe to access. Per default all attributes |
||||
starting with an underscore are considered private as well as the |
||||
special attributes of internal python objects as returned by the |
||||
:func:`is_internal_attribute` function. |
||||
""" |
||||
return not (attr.startswith('_') or is_internal_attribute(obj, attr)) |
||||
|
||||
def is_safe_callable(self, obj): |
||||
"""Check if an object is safely callable. Per default a function is |
||||
considered safe unless the `unsafe_callable` attribute exists and is |
||||
True. Override this method to alter the behavior, but this won't |
||||
affect the `unsafe` decorator from this module. |
||||
""" |
||||
return not (getattr(obj, 'unsafe_callable', False) or |
||||
getattr(obj, 'alters_data', False)) |
||||
|
||||
def call_binop(self, context, operator, left, right): |
||||
"""For intercepted binary operator calls (:meth:`intercepted_binops`) |
||||
this function is executed instead of the builtin operator. This can |
||||
be used to fine tune the behavior of certain operators. |
||||
|
||||
.. versionadded:: 2.6 |
||||
""" |
||||
return self.binop_table[operator](left, right) |
||||
|
||||
def call_unop(self, context, operator, arg): |
||||
"""For intercepted unary operator calls (:meth:`intercepted_unops`) |
||||
this function is executed instead of the builtin operator. This can |
||||
be used to fine tune the behavior of certain operators. |
||||
|
||||
.. versionadded:: 2.6 |
||||
""" |
||||
return self.unop_table[operator](arg) |
||||
|
||||
def getitem(self, obj, argument): |
||||
"""Subscribe an object from sandboxed code.""" |
||||
try: |
||||
return obj[argument] |
||||
except (TypeError, LookupError): |
||||
if isinstance(argument, string_types): |
||||
try: |
||||
attr = str(argument) |
||||
except Exception: |
||||
pass |
||||
else: |
||||
try: |
||||
value = getattr(obj, attr) |
||||
except AttributeError: |
||||
pass |
||||
else: |
||||
if self.is_safe_attribute(obj, argument, value): |
||||
return value |
||||
return self.unsafe_undefined(obj, argument) |
||||
return self.undefined(obj=obj, name=argument) |
||||
|
||||
def getattr(self, obj, attribute): |
||||
"""Subscribe an object from sandboxed code and prefer the |
||||
attribute. The attribute passed *must* be a bytestring. |
||||
""" |
||||
try: |
||||
value = getattr(obj, attribute) |
||||
except AttributeError: |
||||
try: |
||||
return obj[attribute] |
||||
except (TypeError, LookupError): |
||||
pass |
||||
else: |
||||
if self.is_safe_attribute(obj, attribute, value): |
||||
return value |
||||
return self.unsafe_undefined(obj, attribute) |
||||
return self.undefined(obj=obj, name=attribute) |
||||
|
||||
def unsafe_undefined(self, obj, attribute): |
||||
"""Return an undefined object for unsafe attributes.""" |
||||
return self.undefined('access to attribute %r of %r ' |
||||
'object is unsafe.' % ( |
||||
attribute, |
||||
obj.__class__.__name__ |
||||
), name=attribute, obj=obj, exc=SecurityError) |
||||
|
||||
def call(__self, __context, __obj, *args, **kwargs): |
||||
"""Call an object from sandboxed code.""" |
||||
# the double prefixes are to avoid double keyword argument |
||||
# errors when proxying the call. |
||||
if not __self.is_safe_callable(__obj): |
||||
raise SecurityError('%r is not safely callable' % (__obj,)) |
||||
return __context.call(__obj, *args, **kwargs) |
||||
|
||||
|
||||
class ImmutableSandboxedEnvironment(SandboxedEnvironment): |
||||
"""Works exactly like the regular `SandboxedEnvironment` but does not |
||||
permit modifications on the builtin mutable objects `list`, `set`, and |
||||
`dict` by using the :func:`modifies_known_mutable` function. |
||||
""" |
||||
|
||||
def is_safe_attribute(self, obj, attr, value): |
||||
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value): |
||||
return False |
||||
return not modifies_known_mutable(obj, attr) |
@ -0,0 +1,149 @@ |
||||
# -*- coding: utf-8 -*- |
||||
""" |
||||
jinja2.tests |
||||
~~~~~~~~~~~~ |
||||
|
||||
Jinja test functions. Used with the "is" operator. |
||||
|
||||
:copyright: (c) 2010 by the Jinja Team. |
||||
:license: BSD, see LICENSE for more details. |
||||
""" |
||||
import re |
||||
from jinja2.runtime import Undefined |
||||
from jinja2._compat import text_type, string_types, mapping_types |
||||
|
||||
|
||||
number_re = re.compile(r'^-?\d+(\.\d+)?$') |
||||
regex_type = type(number_re) |
||||
|
||||
|
||||
test_callable = callable |
||||
|
||||
|
||||
def test_odd(value): |
||||
"""Return true if the variable is odd.""" |
||||
return value % 2 == 1 |
||||
|
||||
|
||||
def test_even(value): |
||||
"""Return true if the variable is even.""" |
||||
return value % 2 == 0 |
||||
|
||||
|
||||
def test_divisibleby(value, num): |
||||
"""Check if a variable is divisible by a number.""" |
||||
return value % num == 0 |
||||
|
||||
|
||||
def test_defined(value): |
||||
"""Return true if the variable is defined: |
||||
|
||||
.. sourcecode:: jinja |
||||
|
||||
{% if variable is defined %} |
||||
value of variable: {{ variable }} |
||||
{% else %} |
||||
variable is not defined |
||||
{% endif %} |
||||
|
||||
See the :func:`default` filter for a simple way to set undefined |
||||
variables. |
||||
""" |
||||
return not isinstance(value, Undefined) |
||||
|
||||
|
||||
def test_undefined(value): |
||||
"""Like :func:`defined` but the other way round.""" |
||||
return isinstance(value, Undefined) |
||||
|
||||
|
||||
def test_none(value): |
||||
"""Return true if the variable is none.""" |
||||
return value is None |
||||
|
||||
|
||||
def test_lower(value): |
||||
"""Return true if the variable is lowercased.""" |
||||
return text_type(value).islower() |
||||
|
||||
|
||||
def test_upper(value): |
||||
"""Return true if the variable is uppercased.""" |
||||
return text_type(value).isupper() |
||||
|
||||
|
||||
def test_string(value): |
||||
"""Return true if the object is a string.""" |
||||
return isinstance(value, string_types) |
||||
|
||||
|
||||
def test_mapping(value): |
||||
"""Return true if the object is a mapping (dict etc.). |
||||
|
||||
.. versionadded:: 2.6 |
||||
""" |
||||
return isinstance(value, mapping_types) |
||||
|
||||
|
||||
def test_number(value): |
||||
"""Return true if the variable is a number.""" |
||||
return isinstance(value, (int, float, complex)) |
||||
|
||||
|
||||
def test_sequence(value): |
||||
"""Return true if the variable is a sequence. Sequences are variables |
||||
that are iterable. |
||||
""" |
||||
try: |
||||
len(value) |
||||
value.__getitem__ |
||||
except: |
||||
return False |
||||
return True |
||||
|
||||
|
||||
def test_sameas(value, other): |
||||
"""Check if an object points to the same memory address than another |
||||
object: |
||||
|
||||
.. sourcecode:: jinja |
||||
|
||||
{% if foo.attribute is sameas false %} |
||||
the foo attribute really is the `False` singleton |
||||
{% endif %} |
||||
""" |
||||
return value is other |
||||
|
||||
|
||||
def test_iterable(value): |
||||
"""Check if it's possible to iterate over an object.""" |
||||
try: |
||||
iter(value) |
||||
except TypeError: |
||||
return False |
||||
return True |
||||
|
||||
|
||||
def test_escaped(value): |
||||
"""Check if the value is escaped.""" |
||||
return hasattr(value, '__html__') |
||||
|
||||
|
||||
TESTS = { |
||||
'odd': test_odd, |
||||
'even': test_even, |
||||
'divisibleby': test_divisibleby, |
||||
'defined': test_defined, |
||||
'undefined': test_undefined, |
||||
'none': test_none, |
||||
'lower': test_lower, |
||||
'upper': test_upper, |
||||
'string': test_string, |
||||
'mapping': test_mapping, |
||||
'number': test_number, |
||||
'sequence': test_sequence, |
||||
'iterable': test_iterable, |
||||
'callable': test_callable, |
||||
'sameas': test_sameas, |
||||
'escaped': test_escaped |
||||
} |
Loading…
Reference in new issue