mirror of
https://gitee.com/openharmony/third_party_jinja2
synced 2024-11-23 23:29:58 +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 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
|
Version 2.0
|
||||||
-----------
|
-----------
|
||||||
(codename jinjavitus, released on July 17th 2008)
|
(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>`.
|
<jinja-extensions>`.
|
||||||
|
|
||||||
.. autoclass:: Environment([options])
|
.. autoclass:: Environment([options])
|
||||||
:members: from_string, get_template, join_path, extend
|
:members: from_string, get_template, join_path, extend, compile_expression
|
||||||
|
|
||||||
.. attribute:: shared
|
.. attribute:: shared
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
from jinja2 import nodes
|
||||||
from jinja2.defaults import *
|
from jinja2.defaults import *
|
||||||
from jinja2.lexer import get_lexer, TokenStream
|
from jinja2.lexer import get_lexer, TokenStream
|
||||||
from jinja2.parser import Parser
|
from jinja2.parser import Parser
|
||||||
@ -16,7 +17,8 @@ from jinja2.optimizer import optimize
|
|||||||
from jinja2.compiler import generate
|
from jinja2.compiler import generate
|
||||||
from jinja2.runtime import Undefined, Context
|
from jinja2.runtime import Undefined, Context
|
||||||
from jinja2.exceptions import TemplateSyntaxError
|
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
|
# 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),
|
return reduce(lambda s, e: e.preprocess(s, name, filename),
|
||||||
self.extensions.itervalues(), unicode(source))
|
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
|
"""Called by the parser to do the preprocessing and filtering
|
||||||
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
|
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
|
||||||
"""
|
"""
|
||||||
source = self.preprocess(source, name, filename)
|
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():
|
for ext in self.extensions.itervalues():
|
||||||
stream = ext.filter_stream(stream)
|
stream = ext.filter_stream(stream)
|
||||||
if not isinstance(stream, TokenStream):
|
if not isinstance(stream, TokenStream):
|
||||||
@ -407,8 +409,8 @@ class Environment(object):
|
|||||||
if isinstance(source, basestring):
|
if isinstance(source, basestring):
|
||||||
source = self.parse(source, name, filename)
|
source = self.parse(source, name, filename)
|
||||||
if self.optimized:
|
if self.optimized:
|
||||||
node = optimize(source, self)
|
source = optimize(source, self)
|
||||||
source = generate(node, self, name, filename)
|
source = generate(source, self, name, filename)
|
||||||
if raw:
|
if raw:
|
||||||
return source
|
return source
|
||||||
if filename is None:
|
if filename is None:
|
||||||
@ -417,6 +419,48 @@ class Environment(object):
|
|||||||
filename = filename.encode('utf-8')
|
filename = filename.encode('utf-8')
|
||||||
return compile(source, filename, 'exec')
|
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):
|
def join_path(self, template, parent):
|
||||||
"""Join a template with the parent. By default all the lookups are
|
"""Join a template with the parent. By default all the lookups are
|
||||||
relative to the loader root so this method returns the `template`
|
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)
|
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):
|
class TemplateStream(object):
|
||||||
"""A template stream works pretty much like an ordinary python generator
|
"""A template stream works pretty much like an ordinary python generator
|
||||||
but it can buffer multiple items to reduce the number of total iterations.
|
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."""
|
"""Called for strings and template data to normlize it to unicode."""
|
||||||
return newline_re.sub(self.newline_sequence, value)
|
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.
|
"""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)
|
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
||||||
|
|
||||||
def wrap(self, stream, name=None, filename=None):
|
def wrap(self, stream, name=None, filename=None):
|
||||||
@ -426,7 +426,7 @@ class Lexer(object):
|
|||||||
token = operators[value]
|
token = operators[value]
|
||||||
yield Token(lineno, token, 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
|
"""This method tokenizes the text and returns the tokens in a
|
||||||
generator. Use this method if you just want to tokenize a template.
|
generator. Use this method if you just want to tokenize a template.
|
||||||
"""
|
"""
|
||||||
@ -434,7 +434,12 @@ class Lexer(object):
|
|||||||
pos = 0
|
pos = 0
|
||||||
lineno = 1
|
lineno = 1
|
||||||
stack = ['root']
|
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)
|
source_length = len(source)
|
||||||
|
|
||||||
balancing_stack = []
|
balancing_stack = []
|
||||||
|
@ -262,11 +262,6 @@ class CallBlock(Stmt):
|
|||||||
fields = ('call', 'args', 'defaults', 'body')
|
fields = ('call', 'args', 'defaults', 'body')
|
||||||
|
|
||||||
|
|
||||||
class Set(Stmt):
|
|
||||||
"""Allows defining own variables."""
|
|
||||||
fields = ('name', 'expr')
|
|
||||||
|
|
||||||
|
|
||||||
class FilterBlock(Stmt):
|
class FilterBlock(Stmt):
|
||||||
"""Node for filter sections."""
|
"""Node for filter sections."""
|
||||||
fields = ('body', 'filter')
|
fields = ('body', 'filter')
|
||||||
|
@ -23,9 +23,10 @@ class Parser(object):
|
|||||||
extensions and can be used to parse expressions or statements.
|
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.environment = environment
|
||||||
self.stream = environment._tokenize(source, name, filename)
|
self.stream = environment._tokenize(source, name, filename, state)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.closed = False
|
self.closed = False
|
||||||
|
@ -136,6 +136,12 @@ def is_undefined(obj):
|
|||||||
return isinstance(obj, Undefined)
|
return isinstance(obj, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def consume(iterable):
|
||||||
|
"""Consumes an iterable without doing anything with it."""
|
||||||
|
for event in iterable:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def clear_caches():
|
def clear_caches():
|
||||||
"""Jinja2 keeps internal caches for environments and lexers. These are
|
"""Jinja2 keeps internal caches for environments and lexers. These are
|
||||||
used so that Jinja2 doesn't have to recreate environments and lexers all
|
used so that Jinja2 doesn't have to recreate environments and lexers all
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"""
|
"""
|
||||||
import gc
|
import gc
|
||||||
from py.test import raises
|
from py.test import raises
|
||||||
from jinja2 import escape
|
from jinja2 import escape, is_undefined
|
||||||
from jinja2.utils import Cycler
|
from jinja2.utils import Cycler
|
||||||
from jinja2.exceptions import TemplateSyntaxError
|
from jinja2.exceptions import TemplateSyntaxError
|
||||||
|
|
||||||
@ -97,3 +97,14 @@ def test_cycler():
|
|||||||
assert c.current == 2
|
assert c.current == 2
|
||||||
c.reset()
|
c.reset()
|
||||||
assert c.current == 1
|
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