mirror of
https://gitee.com/openharmony/third_party_jinja2
synced 2024-11-23 15:19:46 +00:00
Added support for Environment.compile_expression
.
--HG-- branch : trunk
This commit is contained in:
parent
9efe0819a1
commit
ba6e25a882
3
CHANGES
3
CHANGES
@ -41,6 +41,9 @@ Version 2.1
|
||||
|
||||
- added a joining helper called `joiner`.
|
||||
|
||||
- added a `compile_expression` method to the environment that allows compiling
|
||||
of Jinja expressions into callable Python objects.
|
||||
|
||||
Version 2.0
|
||||
-----------
|
||||
(codename jinjavitus, released on July 17th 2008)
|
||||
|
@ -115,7 +115,7 @@ useful if you want to dig deeper into Jinja2 or :ref:`develop extensions
|
||||
<jinja-extensions>`.
|
||||
|
||||
.. autoclass:: Environment([options])
|
||||
:members: from_string, get_template, join_path, extend
|
||||
:members: from_string, get_template, join_path, extend, compile_expression
|
||||
|
||||
.. attribute:: shared
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
from jinja2 import nodes
|
||||
from jinja2.defaults import *
|
||||
from jinja2.lexer import get_lexer, TokenStream
|
||||
from jinja2.parser import Parser
|
||||
@ -16,7 +17,8 @@ from jinja2.optimizer import optimize
|
||||
from jinja2.compiler import generate
|
||||
from jinja2.runtime import Undefined, Context
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
from jinja2.utils import import_string, LRUCache, Markup, missing, concat
|
||||
from jinja2.utils import import_string, LRUCache, Markup, missing, \
|
||||
concat, consume
|
||||
|
||||
|
||||
# for direct template usage we have up to ten living environments
|
||||
@ -379,12 +381,12 @@ class Environment(object):
|
||||
return reduce(lambda s, e: e.preprocess(s, name, filename),
|
||||
self.extensions.itervalues(), unicode(source))
|
||||
|
||||
def _tokenize(self, source, name, filename=None):
|
||||
def _tokenize(self, source, name, filename=None, state=None):
|
||||
"""Called by the parser to do the preprocessing and filtering
|
||||
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
|
||||
"""
|
||||
source = self.preprocess(source, name, filename)
|
||||
stream = self.lexer.tokenize(source, name, filename)
|
||||
stream = self.lexer.tokenize(source, name, filename, state)
|
||||
for ext in self.extensions.itervalues():
|
||||
stream = ext.filter_stream(stream)
|
||||
if not isinstance(stream, TokenStream):
|
||||
@ -407,8 +409,8 @@ class Environment(object):
|
||||
if isinstance(source, basestring):
|
||||
source = self.parse(source, name, filename)
|
||||
if self.optimized:
|
||||
node = optimize(source, self)
|
||||
source = generate(node, self, name, filename)
|
||||
source = optimize(source, self)
|
||||
source = generate(source, self, name, filename)
|
||||
if raw:
|
||||
return source
|
||||
if filename is None:
|
||||
@ -417,6 +419,48 @@ class Environment(object):
|
||||
filename = filename.encode('utf-8')
|
||||
return compile(source, filename, 'exec')
|
||||
|
||||
def compile_expression(self, source, undefined_to_none=True):
|
||||
"""A handy helper method that returns a callable that accepts keyword
|
||||
arguments that appear as variables in the expression. If called it
|
||||
returns the result of the expression.
|
||||
|
||||
This is useful if applications want to use the same rules as Jinja
|
||||
in template "configuration files" or similar situations.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> env = Environment()
|
||||
>>> expr = env.compile_expression('foo == 42')
|
||||
>>> expr(foo=23)
|
||||
False
|
||||
>>> expr(foo=42)
|
||||
True
|
||||
|
||||
Per default the return value is converted to `None` if the
|
||||
expression returns an undefined value. This can be changed
|
||||
by setting `undefined_to_none` to `False`.
|
||||
|
||||
>>> env.compile_expression('var')() is None
|
||||
True
|
||||
>>> env.compile_expression('var', undefined_to_none=False)()
|
||||
Undefined
|
||||
|
||||
**new in Jinja 2.1**
|
||||
"""
|
||||
parser = Parser(self, source, state='variable')
|
||||
try:
|
||||
expr = parser.parse_expression()
|
||||
if not parser.stream.eos:
|
||||
raise TemplateSyntaxError('chunk after expression',
|
||||
parser.stream.current.lineno,
|
||||
None, None)
|
||||
except TemplateSyntaxError, e:
|
||||
e.source = source
|
||||
raise e
|
||||
body = [nodes.Assign(nodes.Name('result', 'store'), expr, lineno=1)]
|
||||
template = self.from_string(nodes.Template(body, lineno=1))
|
||||
return TemplateExpression(template, undefined_to_none)
|
||||
|
||||
def join_path(self, template, parent):
|
||||
"""Join a template with the parent. By default all the lookups are
|
||||
relative to the loader root so this method returns the `template`
|
||||
@ -699,6 +743,25 @@ class TemplateModule(object):
|
||||
return '<%s %s>' % (self.__class__.__name__, name)
|
||||
|
||||
|
||||
class TemplateExpression(object):
|
||||
"""The :meth:`jinja2.Environment.compile_expression` method returns an
|
||||
instance of this object. It encapsulates the expression-like access
|
||||
to the template with an expression it wraps.
|
||||
"""
|
||||
|
||||
def __init__(self, template, undefined_to_none):
|
||||
self._template = template
|
||||
self._undefined_to_none = undefined_to_none
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
context = self._template.new_context(dict(*args, **kwargs))
|
||||
consume(self._template.root_render_func(context))
|
||||
rv = context.vars['result']
|
||||
if self._undefined_to_none and isinstance(rv, Undefined):
|
||||
rv = None
|
||||
return rv
|
||||
|
||||
|
||||
class TemplateStream(object):
|
||||
"""A template stream works pretty much like an ordinary python generator
|
||||
but it can buffer multiple items to reduce the number of total iterations.
|
||||
|
@ -375,10 +375,10 @@ class Lexer(object):
|
||||
"""Called for strings and template data to normlize it to unicode."""
|
||||
return newline_re.sub(self.newline_sequence, value)
|
||||
|
||||
def tokenize(self, source, name=None, filename=None):
|
||||
def tokenize(self, source, name=None, filename=None, state=None):
|
||||
"""Calls tokeniter + tokenize and wraps it in a token stream.
|
||||
"""
|
||||
stream = self.tokeniter(source, name, filename)
|
||||
stream = self.tokeniter(source, name, filename, state)
|
||||
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
||||
|
||||
def wrap(self, stream, name=None, filename=None):
|
||||
@ -426,7 +426,7 @@ class Lexer(object):
|
||||
token = operators[value]
|
||||
yield Token(lineno, token, value)
|
||||
|
||||
def tokeniter(self, source, name, filename=None):
|
||||
def tokeniter(self, source, name, filename=None, state=None):
|
||||
"""This method tokenizes the text and returns the tokens in a
|
||||
generator. Use this method if you just want to tokenize a template.
|
||||
"""
|
||||
@ -434,7 +434,12 @@ class Lexer(object):
|
||||
pos = 0
|
||||
lineno = 1
|
||||
stack = ['root']
|
||||
statetokens = self.rules['root']
|
||||
if state is not None and state != 'root':
|
||||
assert state in ('variable', 'block'), 'invalid state'
|
||||
stack.append(state + '_begin')
|
||||
else:
|
||||
state = 'root'
|
||||
statetokens = self.rules[stack[-1]]
|
||||
source_length = len(source)
|
||||
|
||||
balancing_stack = []
|
||||
|
@ -262,11 +262,6 @@ class CallBlock(Stmt):
|
||||
fields = ('call', 'args', 'defaults', 'body')
|
||||
|
||||
|
||||
class Set(Stmt):
|
||||
"""Allows defining own variables."""
|
||||
fields = ('name', 'expr')
|
||||
|
||||
|
||||
class FilterBlock(Stmt):
|
||||
"""Node for filter sections."""
|
||||
fields = ('body', 'filter')
|
||||
|
@ -23,9 +23,10 @@ class Parser(object):
|
||||
extensions and can be used to parse expressions or statements.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, source, name=None, filename=None):
|
||||
def __init__(self, environment, source, name=None, filename=None,
|
||||
state=None):
|
||||
self.environment = environment
|
||||
self.stream = environment._tokenize(source, name, filename)
|
||||
self.stream = environment._tokenize(source, name, filename, state)
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.closed = False
|
||||
|
@ -136,6 +136,12 @@ def is_undefined(obj):
|
||||
return isinstance(obj, Undefined)
|
||||
|
||||
|
||||
def consume(iterable):
|
||||
"""Consumes an iterable without doing anything with it."""
|
||||
for event in iterable:
|
||||
pass
|
||||
|
||||
|
||||
def clear_caches():
|
||||
"""Jinja2 keeps internal caches for environments and lexers. These are
|
||||
used so that Jinja2 doesn't have to recreate environments and lexers all
|
||||
|
@ -8,7 +8,7 @@
|
||||
"""
|
||||
import gc
|
||||
from py.test import raises
|
||||
from jinja2 import escape
|
||||
from jinja2 import escape, is_undefined
|
||||
from jinja2.utils import Cycler
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
|
||||
@ -97,3 +97,14 @@ def test_cycler():
|
||||
assert c.current == 2
|
||||
c.reset()
|
||||
assert c.current == 1
|
||||
|
||||
|
||||
def test_expressions(env):
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user