Update to the latest version of jinja2 (#6790)
This commit is contained in:
parent
4fdf08e51c
commit
d17a10c6ac
33
lib/spack/external/jinja2/AUTHORS
vendored
33
lib/spack/external/jinja2/AUTHORS
vendored
@ -1,33 +0,0 @@
|
|||||||
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)
|
|
31
lib/spack/external/jinja2/LICENSE
vendored
31
lib/spack/external/jinja2/LICENSE
vendored
@ -1,31 +0,0 @@
|
|||||||
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.
|
|
51
lib/spack/external/jinja2/README.rst
vendored
51
lib/spack/external/jinja2/README.rst
vendored
@ -1,51 +0,0 @@
|
|||||||
Jinja2
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Jinja2 is a template engine written in pure Python. It provides a
|
|
||||||
`Django`_ inspired non-XML syntax but supports inline expressions and
|
|
||||||
an optional `sandboxed`_ environment.
|
|
||||||
|
|
||||||
Nutshell
|
|
||||||
--------
|
|
||||||
|
|
||||||
Here a small example of a Jinja template:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% block title %}Memberlist{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
Philosophy
|
|
||||||
----------
|
|
||||||
|
|
||||||
Application logic is for the controller, but don't make the template designer's
|
|
||||||
life difficult by restricting functionality too much.
|
|
||||||
|
|
||||||
For more information visit the new `Jinja2 webpage`_ and `documentation`_.
|
|
||||||
|
|
||||||
The `Jinja2 tip`_ is installable via ``pip`` with ``pip install
|
|
||||||
https://github.com/pallets/jinja/zipball/master``.
|
|
||||||
|
|
||||||
.. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
|
|
||||||
.. _Django: http://www.djangoproject.com/
|
|
||||||
.. _Jinja2 webpage: http://jinja.pocoo.org/
|
|
||||||
.. _documentation: http://jinja.pocoo.org/docs/
|
|
||||||
.. _Jinja2 tip: http://jinja.pocoo.org/docs/intro/#as-a-python-egg-via-easy-install
|
|
||||||
|
|
||||||
Builds
|
|
||||||
------
|
|
||||||
|
|
||||||
+---------------------+------------------------------------------------------------------------------+
|
|
||||||
| ``master`` | .. image:: https://travis-ci.org/pallets/jinja.svg?branch=master |
|
|
||||||
| | :target: https://travis-ci.org/pallets/jinja |
|
|
||||||
+---------------------+------------------------------------------------------------------------------+
|
|
||||||
| ``2.9-maintenance`` | .. image:: https://travis-ci.org/pallets/jinja.svg?branch=2.9-maintenance |
|
|
||||||
| | :target: https://travis-ci.org/pallets/jinja |
|
|
||||||
+---------------------+------------------------------------------------------------------------------+
|
|
5
lib/spack/external/jinja2/__init__.py
vendored
5
lib/spack/external/jinja2/__init__.py
vendored
@ -27,7 +27,7 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__version__ = '2.9.6'
|
__version__ = '2.10'
|
||||||
|
|
||||||
# high level interface
|
# high level interface
|
||||||
from jinja2.environment import Environment, Template
|
from jinja2.environment import Environment, Template
|
||||||
@ -48,7 +48,7 @@
|
|||||||
# exceptions
|
# exceptions
|
||||||
from jinja2.exceptions import TemplateError, UndefinedError, \
|
from jinja2.exceptions import TemplateError, UndefinedError, \
|
||||||
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
|
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
|
||||||
TemplateAssertionError
|
TemplateAssertionError, TemplateRuntimeError
|
||||||
|
|
||||||
# decorators and public utilities
|
# decorators and public utilities
|
||||||
from jinja2.filters import environmentfilter, contextfilter, \
|
from jinja2.filters import environmentfilter, contextfilter, \
|
||||||
@ -64,6 +64,7 @@
|
|||||||
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
|
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
|
||||||
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
|
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
|
||||||
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
|
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
|
||||||
|
'TemplateRuntimeError',
|
||||||
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
|
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
|
||||||
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
|
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
|
||||||
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
|
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
|
||||||
|
2
lib/spack/external/jinja2/_identifier.py
vendored
Normal file
2
lib/spack/external/jinja2/_identifier.py
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# generated by scripts/generate_identifier_pattern.py
|
||||||
|
pattern = '·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯'
|
71
lib/spack/external/jinja2/_stringdefs.py
vendored
71
lib/spack/external/jinja2/_stringdefs.py
vendored
File diff suppressed because one or more lines are too long
14
lib/spack/external/jinja2/asyncsupport.py
vendored
14
lib/spack/external/jinja2/asyncsupport.py
vendored
@ -189,9 +189,9 @@ async def auto_aiter(iterable):
|
|||||||
|
|
||||||
class AsyncLoopContext(LoopContextBase):
|
class AsyncLoopContext(LoopContextBase):
|
||||||
|
|
||||||
def __init__(self, async_iterator, after, length, recurse=None,
|
def __init__(self, async_iterator, undefined, after, length, recurse=None,
|
||||||
depth0=0):
|
depth0=0):
|
||||||
LoopContextBase.__init__(self, recurse, depth0)
|
LoopContextBase.__init__(self, undefined, recurse, depth0)
|
||||||
self._async_iterator = async_iterator
|
self._async_iterator = async_iterator
|
||||||
self._after = after
|
self._after = after
|
||||||
self._length = length
|
self._length = length
|
||||||
@ -221,15 +221,16 @@ async def __anext__(self):
|
|||||||
ctx.index0 += 1
|
ctx.index0 += 1
|
||||||
if ctx._after is _last_iteration:
|
if ctx._after is _last_iteration:
|
||||||
raise StopAsyncIteration()
|
raise StopAsyncIteration()
|
||||||
next_elem = ctx._after
|
ctx._before = ctx._current
|
||||||
|
ctx._current = ctx._after
|
||||||
try:
|
try:
|
||||||
ctx._after = await ctx._async_iterator.__anext__()
|
ctx._after = await ctx._async_iterator.__anext__()
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
ctx._after = _last_iteration
|
ctx._after = _last_iteration
|
||||||
return next_elem, ctx
|
return ctx._current, ctx
|
||||||
|
|
||||||
|
|
||||||
async def make_async_loop_context(iterable, recurse=None, depth0=0):
|
async def make_async_loop_context(iterable, undefined, recurse=None, depth0=0):
|
||||||
# Length is more complicated and less efficient in async mode. The
|
# Length is more complicated and less efficient in async mode. The
|
||||||
# reason for this is that we cannot know if length will be used
|
# reason for this is that we cannot know if length will be used
|
||||||
# upfront but because length is a property we cannot lazily execute it
|
# upfront but because length is a property we cannot lazily execute it
|
||||||
@ -251,4 +252,5 @@ async def make_async_loop_context(iterable, recurse=None, depth0=0):
|
|||||||
after = await async_iterator.__anext__()
|
after = await async_iterator.__anext__()
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
after = _last_iteration
|
after = _last_iteration
|
||||||
return AsyncLoopContext(async_iterator, after, length, recurse, depth0)
|
return AsyncLoopContext(async_iterator, undefined, after, length, recurse,
|
||||||
|
depth0)
|
||||||
|
2
lib/spack/external/jinja2/bccache.py
vendored
2
lib/spack/external/jinja2/bccache.py
vendored
@ -297,7 +297,7 @@ class MemcachedBytecodeCache(BytecodeCache):
|
|||||||
Libraries compatible with this class:
|
Libraries compatible with this class:
|
||||||
|
|
||||||
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
|
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
|
||||||
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
|
- `python-memcached <https://www.tummy.com/Community/software/python-memcached/>`_
|
||||||
- `cmemcache <http://gijsbert.org/cmemcache/>`_
|
- `cmemcache <http://gijsbert.org/cmemcache/>`_
|
||||||
|
|
||||||
(Unfortunately the django cache interface is not compatible because it
|
(Unfortunately the django cache interface is not compatible because it
|
||||||
|
88
lib/spack/external/jinja2/compiler.py
vendored
88
lib/spack/external/jinja2/compiler.py
vendored
@ -130,9 +130,10 @@ def __init__(self, node):
|
|||||||
class Frame(object):
|
class Frame(object):
|
||||||
"""Holds compile time information for us."""
|
"""Holds compile time information for us."""
|
||||||
|
|
||||||
def __init__(self, eval_ctx, parent=None):
|
def __init__(self, eval_ctx, parent=None, level=None):
|
||||||
self.eval_ctx = eval_ctx
|
self.eval_ctx = eval_ctx
|
||||||
self.symbols = Symbols(parent and parent.symbols or None)
|
self.symbols = Symbols(parent and parent.symbols or None,
|
||||||
|
level=level)
|
||||||
|
|
||||||
# a toplevel frame is the root + soft frames such as if conditions.
|
# a toplevel frame is the root + soft frames such as if conditions.
|
||||||
self.toplevel = False
|
self.toplevel = False
|
||||||
@ -168,8 +169,10 @@ def copy(self):
|
|||||||
rv.symbols = self.symbols.copy()
|
rv.symbols = self.symbols.copy()
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def inner(self):
|
def inner(self, isolated=False):
|
||||||
"""Return an inner frame."""
|
"""Return an inner frame."""
|
||||||
|
if isolated:
|
||||||
|
return Frame(self.eval_ctx, level=self.symbols.level + 1)
|
||||||
return Frame(self.eval_ctx, self)
|
return Frame(self.eval_ctx, self)
|
||||||
|
|
||||||
def soft(self):
|
def soft(self):
|
||||||
@ -302,6 +305,9 @@ def __init__(self, environment, name, filename, stream=None,
|
|||||||
# Tracks parameter definition blocks
|
# Tracks parameter definition blocks
|
||||||
self._param_def_block = []
|
self._param_def_block = []
|
||||||
|
|
||||||
|
# Tracks the current context.
|
||||||
|
self._context_reference_stack = ['context']
|
||||||
|
|
||||||
# -- Various compilation helpers
|
# -- Various compilation helpers
|
||||||
|
|
||||||
def fail(self, msg, lineno):
|
def fail(self, msg, lineno):
|
||||||
@ -472,8 +478,8 @@ def enter_frame(self, frame):
|
|||||||
if action == VAR_LOAD_PARAMETER:
|
if action == VAR_LOAD_PARAMETER:
|
||||||
pass
|
pass
|
||||||
elif action == VAR_LOAD_RESOLVE:
|
elif action == VAR_LOAD_RESOLVE:
|
||||||
self.writeline('%s = resolve(%r)' %
|
self.writeline('%s = %s(%r)' %
|
||||||
(target, param))
|
(target, self.get_resolve_func(), param))
|
||||||
elif action == VAR_LOAD_ALIAS:
|
elif action == VAR_LOAD_ALIAS:
|
||||||
self.writeline('%s = %s' % (target, param))
|
self.writeline('%s = %s' % (target, param))
|
||||||
elif action == VAR_LOAD_UNDEFINED:
|
elif action == VAR_LOAD_UNDEFINED:
|
||||||
@ -625,6 +631,27 @@ def mark_parameter_stored(self, target):
|
|||||||
if self._param_def_block:
|
if self._param_def_block:
|
||||||
self._param_def_block[-1].discard(target)
|
self._param_def_block[-1].discard(target)
|
||||||
|
|
||||||
|
def push_context_reference(self, target):
|
||||||
|
self._context_reference_stack.append(target)
|
||||||
|
|
||||||
|
def pop_context_reference(self):
|
||||||
|
self._context_reference_stack.pop()
|
||||||
|
|
||||||
|
def get_context_ref(self):
|
||||||
|
return self._context_reference_stack[-1]
|
||||||
|
|
||||||
|
def get_resolve_func(self):
|
||||||
|
target = self._context_reference_stack[-1]
|
||||||
|
if target == 'context':
|
||||||
|
return 'resolve'
|
||||||
|
return '%s.resolve' % target
|
||||||
|
|
||||||
|
def derive_context(self, frame):
|
||||||
|
return '%s.derived(%s)' % (
|
||||||
|
self.get_context_ref(),
|
||||||
|
self.dump_local_context(frame),
|
||||||
|
)
|
||||||
|
|
||||||
def parameter_is_undeclared(self, target):
|
def parameter_is_undeclared(self, target):
|
||||||
"""Checks if a given target is an undeclared parameter."""
|
"""Checks if a given target is an undeclared parameter."""
|
||||||
if not self._param_def_block:
|
if not self._param_def_block:
|
||||||
@ -793,8 +820,11 @@ def visit_Block(self, node, frame):
|
|||||||
self.writeline('if parent_template is None:')
|
self.writeline('if parent_template is None:')
|
||||||
self.indent()
|
self.indent()
|
||||||
level += 1
|
level += 1
|
||||||
context = node.scoped and (
|
|
||||||
'context.derived(%s)' % self.dump_local_context(frame)) or 'context'
|
if node.scoped:
|
||||||
|
context = self.derive_context(frame)
|
||||||
|
else:
|
||||||
|
context = self.get_context_ref()
|
||||||
|
|
||||||
if supports_yield_from and not self.environment.is_async and \
|
if supports_yield_from and not self.environment.is_async and \
|
||||||
frame.buffer is None:
|
frame.buffer is None:
|
||||||
@ -1082,9 +1112,9 @@ def visit_For(self, node, frame):
|
|||||||
self.write(')')
|
self.write(')')
|
||||||
|
|
||||||
if node.recursive:
|
if node.recursive:
|
||||||
self.write(', loop_render_func, depth):')
|
self.write(', undefined, loop_render_func, depth):')
|
||||||
else:
|
else:
|
||||||
self.write(extended_loop and '):' or ':')
|
self.write(extended_loop and ', undefined):' or ':')
|
||||||
|
|
||||||
self.indent()
|
self.indent()
|
||||||
self.enter_frame(loop_frame)
|
self.enter_frame(loop_frame)
|
||||||
@ -1129,6 +1159,13 @@ def visit_If(self, node, frame):
|
|||||||
self.indent()
|
self.indent()
|
||||||
self.blockvisit(node.body, if_frame)
|
self.blockvisit(node.body, if_frame)
|
||||||
self.outdent()
|
self.outdent()
|
||||||
|
for elif_ in node.elif_:
|
||||||
|
self.writeline('elif ', elif_)
|
||||||
|
self.visit(elif_.test, if_frame)
|
||||||
|
self.write(':')
|
||||||
|
self.indent()
|
||||||
|
self.blockvisit(elif_.body, if_frame)
|
||||||
|
self.outdent()
|
||||||
if node.else_:
|
if node.else_:
|
||||||
self.writeline('else:')
|
self.writeline('else:')
|
||||||
self.indent()
|
self.indent()
|
||||||
@ -1348,7 +1385,12 @@ def visit_AssignBlock(self, node, frame):
|
|||||||
self.newline(node)
|
self.newline(node)
|
||||||
self.visit(node.target, frame)
|
self.visit(node.target, frame)
|
||||||
self.write(' = (Markup if context.eval_ctx.autoescape '
|
self.write(' = (Markup if context.eval_ctx.autoescape '
|
||||||
'else identity)(concat(%s))' % block_frame.buffer)
|
'else identity)(')
|
||||||
|
if node.filter is not None:
|
||||||
|
self.visit_Filter(node.filter, block_frame)
|
||||||
|
else:
|
||||||
|
self.write('concat(%s)' % block_frame.buffer)
|
||||||
|
self.write(')')
|
||||||
self.pop_assign_tracking(frame)
|
self.pop_assign_tracking(frame)
|
||||||
self.leave_frame(block_frame)
|
self.leave_frame(block_frame)
|
||||||
|
|
||||||
@ -1373,6 +1415,18 @@ def visit_Name(self, node, frame):
|
|||||||
|
|
||||||
self.write(ref)
|
self.write(ref)
|
||||||
|
|
||||||
|
def visit_NSRef(self, node, frame):
|
||||||
|
# NSRefs can only be used to store values; since they use the normal
|
||||||
|
# `foo.bar` notation they will be parsed as a normal attribute access
|
||||||
|
# when used anywhere but in a `set` context
|
||||||
|
ref = frame.symbols.ref(node.name)
|
||||||
|
self.writeline('if not isinstance(%s, Namespace):' % ref)
|
||||||
|
self.indent()
|
||||||
|
self.writeline('raise TemplateRuntimeError(%r)' %
|
||||||
|
'cannot assign attribute on non-namespace object')
|
||||||
|
self.outdent()
|
||||||
|
self.writeline('%s[%r]' % (ref, node.attr))
|
||||||
|
|
||||||
def visit_Const(self, node, frame):
|
def visit_Const(self, node, frame):
|
||||||
val = node.as_const(frame.eval_ctx)
|
val = node.as_const(frame.eval_ctx)
|
||||||
if isinstance(val, float):
|
if isinstance(val, float):
|
||||||
@ -1631,6 +1685,20 @@ def visit_Scope(self, node, frame):
|
|||||||
self.blockvisit(node.body, scope_frame)
|
self.blockvisit(node.body, scope_frame)
|
||||||
self.leave_frame(scope_frame)
|
self.leave_frame(scope_frame)
|
||||||
|
|
||||||
|
def visit_OverlayScope(self, node, frame):
|
||||||
|
ctx = self.temporary_identifier()
|
||||||
|
self.writeline('%s = %s' % (ctx, self.derive_context(frame)))
|
||||||
|
self.writeline('%s.vars = ' % ctx)
|
||||||
|
self.visit(node.context, frame)
|
||||||
|
self.push_context_reference(ctx)
|
||||||
|
|
||||||
|
scope_frame = frame.inner(isolated=True)
|
||||||
|
scope_frame.symbols.analyze_node(node)
|
||||||
|
self.enter_frame(scope_frame)
|
||||||
|
self.blockvisit(node.body, scope_frame)
|
||||||
|
self.leave_frame(scope_frame)
|
||||||
|
self.pop_context_reference()
|
||||||
|
|
||||||
def visit_EvalContextModifier(self, node, frame):
|
def visit_EvalContextModifier(self, node, frame):
|
||||||
for keyword in node.options:
|
for keyword in node.options:
|
||||||
self.writeline('context.eval_ctx.%s = ' % keyword.key)
|
self.writeline('context.eval_ctx.%s = ' % keyword.key)
|
||||||
|
2
lib/spack/external/jinja2/debug.py
vendored
2
lib/spack/external/jinja2/debug.py
vendored
@ -198,7 +198,7 @@ def translate_exception(exc_info, initial_skip=0):
|
|||||||
def get_jinja_locals(real_locals):
|
def get_jinja_locals(real_locals):
|
||||||
ctx = real_locals.get('context')
|
ctx = real_locals.get('context')
|
||||||
if ctx:
|
if ctx:
|
||||||
locals = ctx.get_all()
|
locals = ctx.get_all().copy()
|
||||||
else:
|
else:
|
||||||
locals = {}
|
locals = {}
|
||||||
|
|
||||||
|
6
lib/spack/external/jinja2/defaults.py
vendored
6
lib/spack/external/jinja2/defaults.py
vendored
@ -9,7 +9,7 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
from jinja2._compat import range_type
|
from jinja2._compat import range_type
|
||||||
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
|
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner, Namespace
|
||||||
|
|
||||||
|
|
||||||
# defaults for the parser / lexer
|
# defaults for the parser / lexer
|
||||||
@ -35,7 +35,8 @@
|
|||||||
'dict': dict,
|
'dict': dict,
|
||||||
'lipsum': generate_lorem_ipsum,
|
'lipsum': generate_lorem_ipsum,
|
||||||
'cycler': Cycler,
|
'cycler': Cycler,
|
||||||
'joiner': Joiner
|
'joiner': Joiner,
|
||||||
|
'namespace': Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -47,6 +48,7 @@
|
|||||||
'truncate.leeway': 5,
|
'truncate.leeway': 5,
|
||||||
'json.dumps_function': None,
|
'json.dumps_function': None,
|
||||||
'json.dumps_kwargs': {'sort_keys': True},
|
'json.dumps_kwargs': {'sort_keys': True},
|
||||||
|
'ext.i18n.trimmed': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
2
lib/spack/external/jinja2/environment.py
vendored
2
lib/spack/external/jinja2/environment.py
vendored
@ -809,7 +809,7 @@ def _load_template(self, name, globals):
|
|||||||
@internalcode
|
@internalcode
|
||||||
def get_template(self, name, parent=None, globals=None):
|
def get_template(self, name, parent=None, globals=None):
|
||||||
"""Load a template from the loader. If a loader is configured this
|
"""Load a template from the loader. If a loader is configured this
|
||||||
method ask the loader for the template and returns a :class:`Template`.
|
method asks the loader for the template and returns a :class:`Template`.
|
||||||
If the `parent` parameter is not `None`, :meth:`join_path` is called
|
If the `parent` parameter is not `None`, :meth:`join_path` is called
|
||||||
to get the real template name before loading.
|
to get the real template name before loading.
|
||||||
|
|
||||||
|
20
lib/spack/external/jinja2/ext.py
vendored
20
lib/spack/external/jinja2/ext.py
vendored
@ -10,6 +10,8 @@
|
|||||||
:copyright: (c) 2017 by the Jinja Team.
|
:copyright: (c) 2017 by the Jinja Team.
|
||||||
:license: BSD.
|
:license: BSD.
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
from jinja2 import nodes
|
from jinja2 import nodes
|
||||||
from jinja2.defaults import BLOCK_START_STRING, \
|
from jinja2.defaults import BLOCK_START_STRING, \
|
||||||
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
|
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
|
||||||
@ -223,6 +225,7 @@ def parse(self, parser):
|
|||||||
plural_expr = None
|
plural_expr = None
|
||||||
plural_expr_assignment = None
|
plural_expr_assignment = None
|
||||||
variables = {}
|
variables = {}
|
||||||
|
trimmed = None
|
||||||
while parser.stream.current.type != 'block_end':
|
while parser.stream.current.type != 'block_end':
|
||||||
if variables:
|
if variables:
|
||||||
parser.stream.expect('comma')
|
parser.stream.expect('comma')
|
||||||
@ -241,6 +244,9 @@ def parse(self, parser):
|
|||||||
if parser.stream.current.type == 'assign':
|
if parser.stream.current.type == 'assign':
|
||||||
next(parser.stream)
|
next(parser.stream)
|
||||||
variables[name.value] = var = parser.parse_expression()
|
variables[name.value] = var = parser.parse_expression()
|
||||||
|
elif trimmed is None and name.value in ('trimmed', 'notrimmed'):
|
||||||
|
trimmed = name.value == 'trimmed'
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
variables[name.value] = var = nodes.Name(name.value, 'load')
|
variables[name.value] = var = nodes.Name(name.value, 'load')
|
||||||
|
|
||||||
@ -256,7 +262,7 @@ def parse(self, parser):
|
|||||||
|
|
||||||
parser.stream.expect('block_end')
|
parser.stream.expect('block_end')
|
||||||
|
|
||||||
plural = plural_names = None
|
plural = None
|
||||||
have_plural = False
|
have_plural = False
|
||||||
referenced = set()
|
referenced = set()
|
||||||
|
|
||||||
@ -297,6 +303,13 @@ def parse(self, parser):
|
|||||||
elif plural_expr is None:
|
elif plural_expr is None:
|
||||||
parser.fail('pluralize without variables', lineno)
|
parser.fail('pluralize without variables', lineno)
|
||||||
|
|
||||||
|
if trimmed is None:
|
||||||
|
trimmed = self.environment.policies['ext.i18n.trimmed']
|
||||||
|
if trimmed:
|
||||||
|
singular = self._trim_whitespace(singular)
|
||||||
|
if plural:
|
||||||
|
plural = self._trim_whitespace(plural)
|
||||||
|
|
||||||
node = self._make_node(singular, plural, variables, plural_expr,
|
node = self._make_node(singular, plural, variables, plural_expr,
|
||||||
bool(referenced),
|
bool(referenced),
|
||||||
num_called_num and have_plural)
|
num_called_num and have_plural)
|
||||||
@ -306,6 +319,9 @@ def parse(self, parser):
|
|||||||
else:
|
else:
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')):
|
||||||
|
return _ws_re.sub(' ', string.strip())
|
||||||
|
|
||||||
def _parse_block(self, parser, allow_pluralize):
|
def _parse_block(self, parser, allow_pluralize):
|
||||||
"""Parse until the next block tag with a given name."""
|
"""Parse until the next block tag with a given name."""
|
||||||
referenced = []
|
referenced = []
|
||||||
@ -583,6 +599,8 @@ def getbool(options, key, default=False):
|
|||||||
auto_reload=False
|
auto_reload=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if getbool(options, 'trimmed'):
|
||||||
|
environment.policies['ext.i18n.trimmed'] = True
|
||||||
if getbool(options, 'newstyle_gettext'):
|
if getbool(options, 'newstyle_gettext'):
|
||||||
environment.newstyle_gettext = True
|
environment.newstyle_gettext = True
|
||||||
|
|
||||||
|
203
lib/spack/external/jinja2/filters.py
vendored
203
lib/spack/external/jinja2/filters.py
vendored
@ -10,9 +10,10 @@
|
|||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
|
import random
|
||||||
|
import warnings
|
||||||
|
|
||||||
from random import choice
|
from itertools import groupby, chain
|
||||||
from itertools import groupby
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
|
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
|
||||||
unicode_urlencode, htmlsafe_json_dumps
|
unicode_urlencode, htmlsafe_json_dumps
|
||||||
@ -52,22 +53,34 @@ def environmentfilter(f):
|
|||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def make_attrgetter(environment, attribute):
|
def ignore_case(value):
|
||||||
|
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
|
||||||
|
to lowercase and returns other types as-is."""
|
||||||
|
return value.lower() if isinstance(value, string_types) else value
|
||||||
|
|
||||||
|
|
||||||
|
def make_attrgetter(environment, attribute, postprocess=None):
|
||||||
"""Returns a callable that looks up the given attribute from a
|
"""Returns a callable that looks up the given attribute from a
|
||||||
passed object with the rules of the environment. Dots are allowed
|
passed object with the rules of the environment. Dots are allowed
|
||||||
to access attributes of attributes. Integer parts in paths are
|
to access attributes of attributes. Integer parts in paths are
|
||||||
looked up as integers.
|
looked up as integers.
|
||||||
"""
|
"""
|
||||||
if not isinstance(attribute, string_types) \
|
if attribute is None:
|
||||||
or ('.' not in attribute and not attribute.isdigit()):
|
attribute = []
|
||||||
return lambda x: environment.getitem(x, attribute)
|
elif isinstance(attribute, string_types):
|
||||||
attribute = attribute.split('.')
|
attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')]
|
||||||
|
else:
|
||||||
|
attribute = [attribute]
|
||||||
|
|
||||||
def attrgetter(item):
|
def attrgetter(item):
|
||||||
for part in attribute:
|
for part in attribute:
|
||||||
if part.isdigit():
|
|
||||||
part = int(part)
|
|
||||||
item = environment.getitem(item, part)
|
item = environment.getitem(item, part)
|
||||||
|
|
||||||
|
if postprocess is not None:
|
||||||
|
item = postprocess(item)
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
return attrgetter
|
return attrgetter
|
||||||
|
|
||||||
|
|
||||||
@ -190,7 +203,7 @@ def do_title(s):
|
|||||||
if item])
|
if item])
|
||||||
|
|
||||||
|
|
||||||
def do_dictsort(value, case_sensitive=False, by='key'):
|
def do_dictsort(value, case_sensitive=False, by='key', reverse=False):
|
||||||
"""Sort a dict and yield (key, value) pairs. Because python dicts are
|
"""Sort a dict and yield (key, value) pairs. Because python dicts are
|
||||||
unsorted you may want to use this function to order them by either
|
unsorted you may want to use this function to order them by either
|
||||||
key or value:
|
key or value:
|
||||||
@ -200,6 +213,9 @@ def do_dictsort(value, case_sensitive=False, by='key'):
|
|||||||
{% for item in mydict|dictsort %}
|
{% for item in mydict|dictsort %}
|
||||||
sort the dict by key, case insensitive
|
sort the dict by key, case insensitive
|
||||||
|
|
||||||
|
{% for item in mydict|dictsort(reverse=true) %}
|
||||||
|
sort the dict by key, case insensitive, reverse order
|
||||||
|
|
||||||
{% for item in mydict|dictsort(true) %}
|
{% for item in mydict|dictsort(true) %}
|
||||||
sort the dict by key, case sensitive
|
sort the dict by key, case sensitive
|
||||||
|
|
||||||
@ -211,20 +227,25 @@ def do_dictsort(value, case_sensitive=False, by='key'):
|
|||||||
elif by == 'value':
|
elif by == 'value':
|
||||||
pos = 1
|
pos = 1
|
||||||
else:
|
else:
|
||||||
raise FilterArgumentError('You can only sort by either '
|
raise FilterArgumentError(
|
||||||
'"key" or "value"')
|
'You can only sort by either "key" or "value"'
|
||||||
|
)
|
||||||
|
|
||||||
def sort_func(item):
|
def sort_func(item):
|
||||||
value = item[pos]
|
value = item[pos]
|
||||||
if isinstance(value, string_types) and not case_sensitive:
|
|
||||||
value = value.lower()
|
if not case_sensitive:
|
||||||
|
value = ignore_case(value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return sorted(value.items(), key=sort_func)
|
return sorted(value.items(), key=sort_func, reverse=reverse)
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
@environmentfilter
|
||||||
def do_sort(environment, value, reverse=False, case_sensitive=False,
|
def do_sort(
|
||||||
attribute=None):
|
environment, value, reverse=False, case_sensitive=False, attribute=None
|
||||||
|
):
|
||||||
"""Sort an iterable. Per default it sorts ascending, if you pass it
|
"""Sort an iterable. Per default it sorts ascending, if you pass it
|
||||||
true as first argument it will reverse the sorting.
|
true as first argument it will reverse the sorting.
|
||||||
|
|
||||||
@ -250,18 +271,85 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
|
|||||||
.. versionchanged:: 2.6
|
.. versionchanged:: 2.6
|
||||||
The `attribute` parameter was added.
|
The `attribute` parameter was added.
|
||||||
"""
|
"""
|
||||||
if not case_sensitive:
|
key_func = make_attrgetter(
|
||||||
def sort_func(item):
|
environment, attribute,
|
||||||
if isinstance(item, string_types):
|
postprocess=ignore_case if not case_sensitive else None
|
||||||
item = item.lower()
|
)
|
||||||
return item
|
return sorted(value, key=key_func, reverse=reverse)
|
||||||
else:
|
|
||||||
sort_func = None
|
|
||||||
if attribute is not None:
|
@environmentfilter
|
||||||
getter = make_attrgetter(environment, attribute)
|
def do_unique(environment, value, case_sensitive=False, attribute=None):
|
||||||
def sort_func(item, processor=sort_func or (lambda x: x)):
|
"""Returns a list of unique items from the the given iterable.
|
||||||
return processor(getter(item))
|
|
||||||
return sorted(value, key=sort_func, reverse=reverse)
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique }}
|
||||||
|
-> ['foo', 'bar', 'foobar']
|
||||||
|
|
||||||
|
The unique items are yielded in the same order as their first occurrence in
|
||||||
|
the iterable passed to the filter.
|
||||||
|
|
||||||
|
:param case_sensitive: Treat upper and lower case strings as distinct.
|
||||||
|
:param attribute: Filter objects with unique values for this attribute.
|
||||||
|
"""
|
||||||
|
getter = make_attrgetter(
|
||||||
|
environment, attribute,
|
||||||
|
postprocess=ignore_case if not case_sensitive else None
|
||||||
|
)
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
for item in value:
|
||||||
|
key = getter(item)
|
||||||
|
|
||||||
|
if key not in seen:
|
||||||
|
seen.add(key)
|
||||||
|
yield item
|
||||||
|
|
||||||
|
|
||||||
|
def _min_or_max(environment, value, func, case_sensitive, attribute):
|
||||||
|
it = iter(value)
|
||||||
|
|
||||||
|
try:
|
||||||
|
first = next(it)
|
||||||
|
except StopIteration:
|
||||||
|
return environment.undefined('No aggregated item, sequence was empty.')
|
||||||
|
|
||||||
|
key_func = make_attrgetter(
|
||||||
|
environment, attribute,
|
||||||
|
ignore_case if not case_sensitive else None
|
||||||
|
)
|
||||||
|
return func(chain([first], it), key=key_func)
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_min(environment, value, case_sensitive=False, attribute=None):
|
||||||
|
"""Return the smallest item from the sequence.
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ [1, 2, 3]|min }}
|
||||||
|
-> 1
|
||||||
|
|
||||||
|
:param case_sensitive: Treat upper and lower case strings as distinct.
|
||||||
|
:param attribute: Get the object with the max value of this attribute.
|
||||||
|
"""
|
||||||
|
return _min_or_max(environment, value, min, case_sensitive, attribute)
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_max(environment, value, case_sensitive=False, attribute=None):
|
||||||
|
"""Return the largest item from the sequence.
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ [1, 2, 3]|max }}
|
||||||
|
-> 3
|
||||||
|
|
||||||
|
:param case_sensitive: Treat upper and lower case strings as distinct.
|
||||||
|
:param attribute: Get the object with the max value of this attribute.
|
||||||
|
"""
|
||||||
|
return _min_or_max(environment, value, max, case_sensitive, attribute)
|
||||||
|
|
||||||
|
|
||||||
def do_default(value, default_value=u'', boolean=False):
|
def do_default(value, default_value=u'', boolean=False):
|
||||||
@ -359,13 +447,13 @@ def do_last(environment, seq):
|
|||||||
return environment.undefined('No last item, sequence was empty.')
|
return environment.undefined('No last item, sequence was empty.')
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
@contextfilter
|
||||||
def do_random(environment, seq):
|
def do_random(context, seq):
|
||||||
"""Return a random item from the sequence."""
|
"""Return a random item from the sequence."""
|
||||||
try:
|
try:
|
||||||
return choice(seq)
|
return random.choice(seq)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return environment.undefined('No random item, sequence was empty.')
|
return context.environment.undefined('No random item, sequence was empty.')
|
||||||
|
|
||||||
|
|
||||||
def do_filesizeformat(value, binary=False):
|
def do_filesizeformat(value, binary=False):
|
||||||
@ -445,21 +533,44 @@ def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
|
|||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
def do_indent(s, width=4, indentfirst=False):
|
def do_indent(
|
||||||
"""Return a copy of the passed string, each line indented by
|
s, width=4, first=False, blank=False, indentfirst=None
|
||||||
4 spaces. The first line is not indented. If you want to
|
):
|
||||||
change the number of spaces or indent the first line too
|
"""Return a copy of the string with each line indented by 4 spaces. The
|
||||||
you can pass additional parameters to the filter:
|
first line and blank lines are not indented by default.
|
||||||
|
|
||||||
.. sourcecode:: jinja
|
:param width: Number of spaces to indent by.
|
||||||
|
:param first: Don't skip indenting the first line.
|
||||||
|
:param blank: Don't skip indenting empty lines.
|
||||||
|
|
||||||
{{ mytext|indent(2, true) }}
|
.. versionchanged:: 2.10
|
||||||
indent by two spaces and indent the first line too.
|
Blank lines are not indented by default.
|
||||||
|
|
||||||
|
Rename the ``indentfirst`` argument to ``first``.
|
||||||
"""
|
"""
|
||||||
|
if indentfirst is not None:
|
||||||
|
warnings.warn(DeprecationWarning(
|
||||||
|
'The "indentfirst" argument is renamed to "first".'
|
||||||
|
), stacklevel=2)
|
||||||
|
first = indentfirst
|
||||||
|
|
||||||
|
s += u'\n' # this quirk is necessary for splitlines method
|
||||||
indention = u' ' * width
|
indention = u' ' * width
|
||||||
rv = (u'\n' + indention).join(s.splitlines())
|
|
||||||
if indentfirst:
|
if blank:
|
||||||
|
rv = (u'\n' + indention).join(s.splitlines())
|
||||||
|
else:
|
||||||
|
lines = s.splitlines()
|
||||||
|
rv = lines.pop(0)
|
||||||
|
|
||||||
|
if lines:
|
||||||
|
rv += u'\n' + u'\n'.join(
|
||||||
|
indention + line if line else line for line in lines
|
||||||
|
)
|
||||||
|
|
||||||
|
if first:
|
||||||
rv = indention + rv
|
rv = indention + rv
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
@ -865,6 +976,9 @@ def do_select(*args, **kwargs):
|
|||||||
|
|
||||||
{{ numbers|select("odd") }}
|
{{ numbers|select("odd") }}
|
||||||
{{ numbers|select("odd") }}
|
{{ numbers|select("odd") }}
|
||||||
|
{{ numbers|select("divisibleby", 3) }}
|
||||||
|
{{ numbers|select("lessthan", 42) }}
|
||||||
|
{{ strings|select("equalto", "mystring") }}
|
||||||
|
|
||||||
.. versionadded:: 2.7
|
.. versionadded:: 2.7
|
||||||
"""
|
"""
|
||||||
@ -1045,6 +1159,8 @@ def select_or_reject(args, kwargs, modfunc, lookup_attr):
|
|||||||
'list': do_list,
|
'list': do_list,
|
||||||
'lower': do_lower,
|
'lower': do_lower,
|
||||||
'map': do_map,
|
'map': do_map,
|
||||||
|
'min': do_min,
|
||||||
|
'max': do_max,
|
||||||
'pprint': do_pprint,
|
'pprint': do_pprint,
|
||||||
'random': do_random,
|
'random': do_random,
|
||||||
'reject': do_reject,
|
'reject': do_reject,
|
||||||
@ -1063,6 +1179,7 @@ def select_or_reject(args, kwargs, modfunc, lookup_attr):
|
|||||||
'title': do_title,
|
'title': do_title,
|
||||||
'trim': do_trim,
|
'trim': do_trim,
|
||||||
'truncate': do_truncate,
|
'truncate': do_truncate,
|
||||||
|
'unique': do_unique,
|
||||||
'upper': do_upper,
|
'upper': do_upper,
|
||||||
'urlencode': do_urlencode,
|
'urlencode': do_urlencode,
|
||||||
'urlize': do_urlize,
|
'urlize': do_urlize,
|
||||||
|
25
lib/spack/external/jinja2/idtracking.py
vendored
25
lib/spack/external/jinja2/idtracking.py
vendored
@ -24,11 +24,13 @@ def symbols_for_node(node, parent_symbols=None):
|
|||||||
|
|
||||||
class Symbols(object):
|
class Symbols(object):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None, level=None):
|
||||||
if parent is None:
|
if level is None:
|
||||||
self.level = 0
|
if parent is None:
|
||||||
else:
|
level = 0
|
||||||
self.level = parent.level + 1
|
else:
|
||||||
|
level = parent.level + 1
|
||||||
|
self.level = level
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.refs = {}
|
self.refs = {}
|
||||||
self.loads = {}
|
self.loads = {}
|
||||||
@ -167,6 +169,10 @@ def visit_CallBlock(self, node, **kwargs):
|
|||||||
for child in node.iter_child_nodes(exclude=('call',)):
|
for child in node.iter_child_nodes(exclude=('call',)):
|
||||||
self.sym_visitor.visit(child)
|
self.sym_visitor.visit(child)
|
||||||
|
|
||||||
|
def visit_OverlayScope(self, node, **kwargs):
|
||||||
|
for child in node.body:
|
||||||
|
self.sym_visitor.visit(child)
|
||||||
|
|
||||||
def visit_For(self, node, for_branch='body', **kwargs):
|
def visit_For(self, node, for_branch='body', **kwargs):
|
||||||
if for_branch == 'body':
|
if for_branch == 'body':
|
||||||
self.sym_visitor.visit(node.target, store_as_param=True)
|
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||||
@ -209,6 +215,9 @@ def visit_Name(self, node, store_as_param=False, **kwargs):
|
|||||||
elif node.ctx == 'load':
|
elif node.ctx == 'load':
|
||||||
self.symbols.load(node.name)
|
self.symbols.load(node.name)
|
||||||
|
|
||||||
|
def visit_NSRef(self, node, **kwargs):
|
||||||
|
self.symbols.load(node.name)
|
||||||
|
|
||||||
def visit_If(self, node, **kwargs):
|
def visit_If(self, node, **kwargs):
|
||||||
self.visit(node.test, **kwargs)
|
self.visit(node.test, **kwargs)
|
||||||
|
|
||||||
@ -222,9 +231,10 @@ def inner_visit(nodes):
|
|||||||
return rv
|
return rv
|
||||||
|
|
||||||
body_symbols = inner_visit(node.body)
|
body_symbols = inner_visit(node.body)
|
||||||
|
elif_symbols = inner_visit(node.elif_)
|
||||||
else_symbols = inner_visit(node.else_ or ())
|
else_symbols = inner_visit(node.else_ or ())
|
||||||
|
|
||||||
self.symbols.branch_update([body_symbols, else_symbols])
|
self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
|
||||||
|
|
||||||
def visit_Macro(self, node, **kwargs):
|
def visit_Macro(self, node, **kwargs):
|
||||||
self.symbols.store(node.name)
|
self.symbols.store(node.name)
|
||||||
@ -271,3 +281,6 @@ def visit_Scope(self, node, **kwargs):
|
|||||||
|
|
||||||
def visit_Block(self, node, **kwargs):
|
def visit_Block(self, node, **kwargs):
|
||||||
"""Stop visiting at blocks."""
|
"""Stop visiting at blocks."""
|
||||||
|
|
||||||
|
def visit_OverlayScope(self, node, **kwargs):
|
||||||
|
"""Do not visit into overlay scopes."""
|
||||||
|
56
lib/spack/external/jinja2/lexer.py
vendored
56
lib/spack/external/jinja2/lexer.py
vendored
@ -15,14 +15,12 @@
|
|||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
|
|
||||||
from operator import itemgetter
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
|
from jinja2._compat import implements_iterator, intern, iteritems, text_type
|
||||||
from jinja2.exceptions import TemplateSyntaxError
|
from jinja2.exceptions import TemplateSyntaxError
|
||||||
from jinja2.utils import LRUCache
|
from jinja2.utils import LRUCache
|
||||||
from jinja2._compat import iteritems, implements_iterator, text_type, intern
|
|
||||||
|
|
||||||
|
|
||||||
# cache for the lexers. Exists in order to be able to have multiple
|
# cache for the lexers. Exists in order to be able to have multiple
|
||||||
# environments with the same lexer
|
# environments with the same lexer
|
||||||
@ -34,28 +32,25 @@
|
|||||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
||||||
integer_re = re.compile(r'\d+')
|
integer_re = re.compile(r'\d+')
|
||||||
|
|
||||||
def _make_name_re():
|
try:
|
||||||
try:
|
# check if this Python supports Unicode identifiers
|
||||||
compile('föö', '<unknown>', 'eval')
|
compile('föö', '<unknown>', 'eval')
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
return re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
|
# no Unicode support, use ASCII identifiers
|
||||||
|
name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
|
||||||
|
check_ident = False
|
||||||
|
else:
|
||||||
|
# Unicode support, build a pattern to match valid characters, and set flag
|
||||||
|
# to use str.isidentifier to validate during lexing
|
||||||
|
from jinja2 import _identifier
|
||||||
|
name_re = re.compile(r'[\w{0}]+'.format(_identifier.pattern))
|
||||||
|
check_ident = True
|
||||||
|
# remove the pattern from memory after building the regex
|
||||||
|
import sys
|
||||||
|
del sys.modules['jinja2._identifier']
|
||||||
import jinja2
|
import jinja2
|
||||||
from jinja2 import _stringdefs
|
del jinja2._identifier
|
||||||
name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
|
del _identifier
|
||||||
_stringdefs.xid_continue))
|
|
||||||
|
|
||||||
# Save some memory here
|
|
||||||
sys.modules.pop('jinja2._stringdefs')
|
|
||||||
del _stringdefs
|
|
||||||
del jinja2._stringdefs
|
|
||||||
|
|
||||||
return name_re
|
|
||||||
|
|
||||||
# we use the unicode identifier rule if this python version is able
|
|
||||||
# to handle unicode identifiers, otherwise the standard ASCII one.
|
|
||||||
name_re = _make_name_re()
|
|
||||||
del _make_name_re
|
|
||||||
|
|
||||||
float_re = re.compile(r'(?<!\.)\d+\.\d+')
|
float_re = re.compile(r'(?<!\.)\d+\.\d+')
|
||||||
newline_re = re.compile(r'(\r\n|\r|\n)')
|
newline_re = re.compile(r'(\r\n|\r|\n)')
|
||||||
@ -352,7 +347,10 @@ def skip_if(self, expr):
|
|||||||
return self.next_if(expr) is not None
|
return self.next_if(expr) is not None
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
"""Go one token ahead and return the old one"""
|
"""Go one token ahead and return the old one.
|
||||||
|
|
||||||
|
Use the built-in :func:`next` instead of calling this directly.
|
||||||
|
"""
|
||||||
rv = self.current
|
rv = self.current
|
||||||
if self._pushed:
|
if self._pushed:
|
||||||
self.current = self._pushed.popleft()
|
self.current = self._pushed.popleft()
|
||||||
@ -577,6 +575,10 @@ def wrap(self, stream, name=None, filename=None):
|
|||||||
token = value
|
token = value
|
||||||
elif token == 'name':
|
elif token == 'name':
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
if check_ident and not value.isidentifier():
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
'Invalid character in identifier',
|
||||||
|
lineno, name, filename)
|
||||||
elif token == 'string':
|
elif token == 'string':
|
||||||
# try to unescape string
|
# try to unescape string
|
||||||
try:
|
try:
|
||||||
|
220
lib/spack/external/jinja2/nativetypes.py
vendored
Normal file
220
lib/spack/external/jinja2/nativetypes.py
vendored
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import sys
|
||||||
|
from ast import literal_eval
|
||||||
|
from itertools import islice, chain
|
||||||
|
from jinja2 import nodes
|
||||||
|
from jinja2._compat import text_type
|
||||||
|
from jinja2.compiler import CodeGenerator, has_safe_repr
|
||||||
|
from jinja2.environment import Environment, Template
|
||||||
|
from jinja2.utils import concat, escape
|
||||||
|
|
||||||
|
|
||||||
|
def native_concat(nodes):
|
||||||
|
"""Return a native Python type from the list of compiled nodes. If the
|
||||||
|
result is a single node, its value is returned. Otherwise, the nodes are
|
||||||
|
concatenated as strings. If the result can be parsed with
|
||||||
|
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
|
||||||
|
string is returned.
|
||||||
|
"""
|
||||||
|
head = list(islice(nodes, 2))
|
||||||
|
|
||||||
|
if not head:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(head) == 1:
|
||||||
|
out = head[0]
|
||||||
|
else:
|
||||||
|
out = u''.join([text_type(v) for v in chain(head, nodes)])
|
||||||
|
|
||||||
|
try:
|
||||||
|
return literal_eval(out)
|
||||||
|
except (ValueError, SyntaxError, MemoryError):
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class NativeCodeGenerator(CodeGenerator):
|
||||||
|
"""A code generator which avoids injecting ``to_string()`` calls around the
|
||||||
|
internal code Jinja uses to render templates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def visit_Output(self, node, frame):
|
||||||
|
"""Same as :meth:`CodeGenerator.visit_Output`, but do not call
|
||||||
|
``to_string`` on output nodes in generated code.
|
||||||
|
"""
|
||||||
|
if self.has_known_extends and frame.require_output_check:
|
||||||
|
return
|
||||||
|
|
||||||
|
finalize = self.environment.finalize
|
||||||
|
finalize_context = getattr(finalize, 'contextfunction', False)
|
||||||
|
finalize_eval = getattr(finalize, 'evalcontextfunction', False)
|
||||||
|
finalize_env = getattr(finalize, 'environmentfunction', False)
|
||||||
|
|
||||||
|
if finalize is not None:
|
||||||
|
if finalize_context or finalize_eval:
|
||||||
|
const_finalize = None
|
||||||
|
elif finalize_env:
|
||||||
|
def const_finalize(x):
|
||||||
|
return finalize(self.environment, x)
|
||||||
|
else:
|
||||||
|
const_finalize = finalize
|
||||||
|
else:
|
||||||
|
def const_finalize(x):
|
||||||
|
return x
|
||||||
|
|
||||||
|
# If we are inside a frame that requires output checking, we do so.
|
||||||
|
outdent_later = False
|
||||||
|
|
||||||
|
if frame.require_output_check:
|
||||||
|
self.writeline('if parent_template is None:')
|
||||||
|
self.indent()
|
||||||
|
outdent_later = True
|
||||||
|
|
||||||
|
# Try to evaluate as many chunks as possible into a static string at
|
||||||
|
# compile time.
|
||||||
|
body = []
|
||||||
|
|
||||||
|
for child in node.nodes:
|
||||||
|
try:
|
||||||
|
if const_finalize is None:
|
||||||
|
raise nodes.Impossible()
|
||||||
|
|
||||||
|
const = child.as_const(frame.eval_ctx)
|
||||||
|
if not has_safe_repr(const):
|
||||||
|
raise nodes.Impossible()
|
||||||
|
except nodes.Impossible:
|
||||||
|
body.append(child)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# the frame can't be volatile here, because otherwise the as_const
|
||||||
|
# function would raise an Impossible exception at that point
|
||||||
|
try:
|
||||||
|
if frame.eval_ctx.autoescape:
|
||||||
|
if hasattr(const, '__html__'):
|
||||||
|
const = const.__html__()
|
||||||
|
else:
|
||||||
|
const = escape(const)
|
||||||
|
|
||||||
|
const = const_finalize(const)
|
||||||
|
except Exception:
|
||||||
|
# if something goes wrong here we evaluate the node at runtime
|
||||||
|
# for easier debugging
|
||||||
|
body.append(child)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if body and isinstance(body[-1], list):
|
||||||
|
body[-1].append(const)
|
||||||
|
else:
|
||||||
|
body.append([const])
|
||||||
|
|
||||||
|
# if we have less than 3 nodes or a buffer we yield or extend/append
|
||||||
|
if len(body) < 3 or frame.buffer is not None:
|
||||||
|
if frame.buffer is not None:
|
||||||
|
# for one item we append, for more we extend
|
||||||
|
if len(body) == 1:
|
||||||
|
self.writeline('%s.append(' % frame.buffer)
|
||||||
|
else:
|
||||||
|
self.writeline('%s.extend((' % frame.buffer)
|
||||||
|
|
||||||
|
self.indent()
|
||||||
|
|
||||||
|
for item in body:
|
||||||
|
if isinstance(item, list):
|
||||||
|
val = repr(native_concat(item))
|
||||||
|
|
||||||
|
if frame.buffer is None:
|
||||||
|
self.writeline('yield ' + val)
|
||||||
|
else:
|
||||||
|
self.writeline(val + ',')
|
||||||
|
else:
|
||||||
|
if frame.buffer is None:
|
||||||
|
self.writeline('yield ', item)
|
||||||
|
else:
|
||||||
|
self.newline(item)
|
||||||
|
|
||||||
|
close = 0
|
||||||
|
|
||||||
|
if finalize is not None:
|
||||||
|
self.write('environment.finalize(')
|
||||||
|
|
||||||
|
if finalize_context:
|
||||||
|
self.write('context, ')
|
||||||
|
|
||||||
|
close += 1
|
||||||
|
|
||||||
|
self.visit(item, frame)
|
||||||
|
|
||||||
|
if close > 0:
|
||||||
|
self.write(')' * close)
|
||||||
|
|
||||||
|
if frame.buffer is not None:
|
||||||
|
self.write(',')
|
||||||
|
|
||||||
|
if frame.buffer is not None:
|
||||||
|
# close the open parentheses
|
||||||
|
self.outdent()
|
||||||
|
self.writeline(len(body) == 1 and ')' or '))')
|
||||||
|
|
||||||
|
# otherwise we create a format string as this is faster in that case
|
||||||
|
else:
|
||||||
|
format = []
|
||||||
|
arguments = []
|
||||||
|
|
||||||
|
for item in body:
|
||||||
|
if isinstance(item, list):
|
||||||
|
format.append(native_concat(item).replace('%', '%%'))
|
||||||
|
else:
|
||||||
|
format.append('%s')
|
||||||
|
arguments.append(item)
|
||||||
|
|
||||||
|
self.writeline('yield ')
|
||||||
|
self.write(repr(concat(format)) + ' % (')
|
||||||
|
self.indent()
|
||||||
|
|
||||||
|
for argument in arguments:
|
||||||
|
self.newline(argument)
|
||||||
|
close = 0
|
||||||
|
|
||||||
|
if finalize is not None:
|
||||||
|
self.write('environment.finalize(')
|
||||||
|
|
||||||
|
if finalize_context:
|
||||||
|
self.write('context, ')
|
||||||
|
elif finalize_eval:
|
||||||
|
self.write('context.eval_ctx, ')
|
||||||
|
elif finalize_env:
|
||||||
|
self.write('environment, ')
|
||||||
|
|
||||||
|
close += 1
|
||||||
|
|
||||||
|
self.visit(argument, frame)
|
||||||
|
self.write(')' * close + ', ')
|
||||||
|
|
||||||
|
self.outdent()
|
||||||
|
self.writeline(')')
|
||||||
|
|
||||||
|
if outdent_later:
|
||||||
|
self.outdent()
|
||||||
|
|
||||||
|
|
||||||
|
class NativeTemplate(Template):
|
||||||
|
def render(self, *args, **kwargs):
|
||||||
|
"""Render the template to produce a native Python type. If the result
|
||||||
|
is a single node, its value is returned. Otherwise, the nodes are
|
||||||
|
concatenated as strings. If the result can be parsed with
|
||||||
|
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
|
||||||
|
string is returned.
|
||||||
|
"""
|
||||||
|
vars = dict(*args, **kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return native_concat(self.root_render_func(self.new_context(vars)))
|
||||||
|
except Exception:
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
|
||||||
|
return self.environment.handle_exception(exc_info, True)
|
||||||
|
|
||||||
|
|
||||||
|
class NativeEnvironment(Environment):
|
||||||
|
"""An environment that renders templates to native Python types."""
|
||||||
|
|
||||||
|
code_generator_class = NativeCodeGenerator
|
||||||
|
template_class = NativeTemplate
|
94
lib/spack/external/jinja2/nodes.py
vendored
94
lib/spack/external/jinja2/nodes.py
vendored
@ -314,7 +314,7 @@ class For(Stmt):
|
|||||||
|
|
||||||
class If(Stmt):
|
class If(Stmt):
|
||||||
"""If `test` is true, `body` is rendered, else `else_`."""
|
"""If `test` is true, `body` is rendered, else `else_`."""
|
||||||
fields = ('test', 'body', 'else_')
|
fields = ('test', 'body', 'elif_', 'else_')
|
||||||
|
|
||||||
|
|
||||||
class Macro(Stmt):
|
class Macro(Stmt):
|
||||||
@ -387,7 +387,7 @@ class Assign(Stmt):
|
|||||||
|
|
||||||
class AssignBlock(Stmt):
|
class AssignBlock(Stmt):
|
||||||
"""Assigns a block to a target."""
|
"""Assigns a block to a target."""
|
||||||
fields = ('target', 'body')
|
fields = ('target', 'filter', 'body')
|
||||||
|
|
||||||
|
|
||||||
class Expr(Node):
|
class Expr(Node):
|
||||||
@ -465,6 +465,18 @@ def can_assign(self):
|
|||||||
'True', 'False', 'None')
|
'True', 'False', 'None')
|
||||||
|
|
||||||
|
|
||||||
|
class NSRef(Expr):
|
||||||
|
"""Reference to a namespace value assignment"""
|
||||||
|
fields = ('name', 'attr')
|
||||||
|
|
||||||
|
def can_assign(self):
|
||||||
|
# We don't need any special checks here; NSRef assignments have a
|
||||||
|
# runtime check to ensure the target is a namespace object which will
|
||||||
|
# have been checked already as it is created using a normal assignment
|
||||||
|
# which goes through a `Name` node.
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Literal(Expr):
|
class Literal(Expr):
|
||||||
"""Baseclass for literals."""
|
"""Baseclass for literals."""
|
||||||
abstract = True
|
abstract = True
|
||||||
@ -587,6 +599,25 @@ def as_const(self, eval_ctx=None):
|
|||||||
return self.expr2.as_const(eval_ctx)
|
return self.expr2.as_const(eval_ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def args_as_const(node, eval_ctx):
|
||||||
|
args = [x.as_const(eval_ctx) for x in node.args]
|
||||||
|
kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
|
||||||
|
|
||||||
|
if node.dyn_args is not None:
|
||||||
|
try:
|
||||||
|
args.extend(node.dyn_args.as_const(eval_ctx))
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
if node.dyn_kwargs is not None:
|
||||||
|
try:
|
||||||
|
kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
return args, kwargs
|
||||||
|
|
||||||
|
|
||||||
class Filter(Expr):
|
class Filter(Expr):
|
||||||
"""This node applies a filter on an expression. `name` is the name of
|
"""This node applies a filter on an expression. `name` is the name of
|
||||||
the filter, the rest of the fields are the same as for :class:`Call`.
|
the filter, the rest of the fields are the same as for :class:`Call`.
|
||||||
@ -594,44 +625,41 @@ class Filter(Expr):
|
|||||||
If the `node` of a filter is `None` the contents of the last buffer are
|
If the `node` of a filter is `None` the contents of the last buffer are
|
||||||
filtered. Buffers are created by macros and filter blocks.
|
filtered. Buffers are created by macros and filter blocks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||||
|
|
||||||
def as_const(self, eval_ctx=None):
|
def as_const(self, eval_ctx=None):
|
||||||
eval_ctx = get_eval_context(self, eval_ctx)
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
|
||||||
if eval_ctx.volatile or self.node is None:
|
if eval_ctx.volatile or self.node is None:
|
||||||
raise Impossible()
|
raise Impossible()
|
||||||
|
|
||||||
# we have to be careful here because we call filter_ below.
|
# we have to be careful here because we call filter_ below.
|
||||||
# if this variable would be called filter, 2to3 would wrap the
|
# if this variable would be called filter, 2to3 would wrap the
|
||||||
# call in a list beause it is assuming we are talking about the
|
# call in a list beause it is assuming we are talking about the
|
||||||
# builtin filter function here which no longer returns a list in
|
# builtin filter function here which no longer returns a list in
|
||||||
# python 3. because of that, do not rename filter_ to filter!
|
# python 3. because of that, do not rename filter_ to filter!
|
||||||
filter_ = self.environment.filters.get(self.name)
|
filter_ = self.environment.filters.get(self.name)
|
||||||
|
|
||||||
if filter_ is None or getattr(filter_, 'contextfilter', False):
|
if filter_ is None or getattr(filter_, 'contextfilter', False):
|
||||||
raise Impossible()
|
raise Impossible()
|
||||||
|
|
||||||
# We cannot constant handle async filters, so we need to make sure
|
# We cannot constant handle async filters, so we need to make sure
|
||||||
# to not go down this path.
|
# to not go down this path.
|
||||||
if eval_ctx.environment.is_async and \
|
if (
|
||||||
getattr(filter_, 'asyncfiltervariant', False):
|
eval_ctx.environment.is_async
|
||||||
|
and getattr(filter_, 'asyncfiltervariant', False)
|
||||||
|
):
|
||||||
raise Impossible()
|
raise Impossible()
|
||||||
|
|
||||||
obj = self.node.as_const(eval_ctx)
|
args, kwargs = args_as_const(self, eval_ctx)
|
||||||
args = [obj] + [x.as_const(eval_ctx) for x in self.args]
|
args.insert(0, self.node.as_const(eval_ctx))
|
||||||
|
|
||||||
if getattr(filter_, 'evalcontextfilter', False):
|
if getattr(filter_, 'evalcontextfilter', False):
|
||||||
args.insert(0, eval_ctx)
|
args.insert(0, eval_ctx)
|
||||||
elif getattr(filter_, 'environmentfilter', False):
|
elif getattr(filter_, 'environmentfilter', False):
|
||||||
args.insert(0, self.environment)
|
args.insert(0, self.environment)
|
||||||
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
|
|
||||||
if self.dyn_args is not None:
|
|
||||||
try:
|
|
||||||
args.extend(self.dyn_args.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
if self.dyn_kwargs is not None:
|
|
||||||
try:
|
|
||||||
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
|
|
||||||
except Exception:
|
|
||||||
raise Impossible()
|
|
||||||
try:
|
try:
|
||||||
return filter_(*args, **kwargs)
|
return filter_(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -642,8 +670,24 @@ class Test(Expr):
|
|||||||
"""Applies a test on an expression. `name` is the name of the test, the
|
"""Applies a test on an expression. `name` is the name of the test, the
|
||||||
rest of the fields are the same as for :class:`Call`.
|
rest of the fields are the same as for :class:`Call`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
test = self.environment.tests.get(self.name)
|
||||||
|
|
||||||
|
if test is None:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
args, kwargs = args_as_const(self, eval_ctx)
|
||||||
|
args.insert(0, self.node.as_const(eval_ctx))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return test(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
|
||||||
class Call(Expr):
|
class Call(Expr):
|
||||||
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
|
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
|
||||||
@ -914,6 +958,22 @@ class Scope(Stmt):
|
|||||||
fields = ('body',)
|
fields = ('body',)
|
||||||
|
|
||||||
|
|
||||||
|
class OverlayScope(Stmt):
|
||||||
|
"""An overlay scope for extensions. This is a largely unoptimized scope
|
||||||
|
that however can be used to introduce completely arbitrary variables into
|
||||||
|
a sub scope from a dictionary or dictionary like object. The `context`
|
||||||
|
field has to evaluate to a dictionary object.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
OverlayScope(context=self.call_method('get_context'),
|
||||||
|
body=[...])
|
||||||
|
|
||||||
|
.. versionadded:: 2.10
|
||||||
|
"""
|
||||||
|
fields = ('context', 'body')
|
||||||
|
|
||||||
|
|
||||||
class EvalContextModifier(Stmt):
|
class EvalContextModifier(Stmt):
|
||||||
"""Modifies the eval context. For each option that should be modified,
|
"""Modifies the eval context. For each option that should be modified,
|
||||||
a :class:`Keyword` has to be added to the :attr:`options` list.
|
a :class:`Keyword` has to be added to the :attr:`options` list.
|
||||||
|
33
lib/spack/external/jinja2/parser.py
vendored
33
lib/spack/external/jinja2/parser.py
vendored
@ -176,13 +176,14 @@ def parse_statements(self, end_tokens, drop_needle=False):
|
|||||||
def parse_set(self):
|
def parse_set(self):
|
||||||
"""Parse an assign statement."""
|
"""Parse an assign statement."""
|
||||||
lineno = next(self.stream).lineno
|
lineno = next(self.stream).lineno
|
||||||
target = self.parse_assign_target()
|
target = self.parse_assign_target(with_namespace=True)
|
||||||
if self.stream.skip_if('assign'):
|
if self.stream.skip_if('assign'):
|
||||||
expr = self.parse_tuple()
|
expr = self.parse_tuple()
|
||||||
return nodes.Assign(target, expr, lineno=lineno)
|
return nodes.Assign(target, expr, lineno=lineno)
|
||||||
|
filter_node = self.parse_filter(None)
|
||||||
body = self.parse_statements(('name:endset',),
|
body = self.parse_statements(('name:endset',),
|
||||||
drop_needle=True)
|
drop_needle=True)
|
||||||
return nodes.AssignBlock(target, body, lineno=lineno)
|
return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
|
||||||
|
|
||||||
def parse_for(self):
|
def parse_for(self):
|
||||||
"""Parse a for loop."""
|
"""Parse a for loop."""
|
||||||
@ -210,17 +211,16 @@ def parse_if(self):
|
|||||||
node.test = self.parse_tuple(with_condexpr=False)
|
node.test = self.parse_tuple(with_condexpr=False)
|
||||||
node.body = self.parse_statements(('name:elif', 'name:else',
|
node.body = self.parse_statements(('name:elif', 'name:else',
|
||||||
'name:endif'))
|
'name:endif'))
|
||||||
|
node.elif_ = []
|
||||||
|
node.else_ = []
|
||||||
token = next(self.stream)
|
token = next(self.stream)
|
||||||
if token.test('name:elif'):
|
if token.test('name:elif'):
|
||||||
new_node = nodes.If(lineno=self.stream.current.lineno)
|
node = nodes.If(lineno=self.stream.current.lineno)
|
||||||
node.else_ = [new_node]
|
result.elif_.append(node)
|
||||||
node = new_node
|
|
||||||
continue
|
continue
|
||||||
elif token.test('name:else'):
|
elif token.test('name:else'):
|
||||||
node.else_ = self.parse_statements(('name:endif',),
|
result.else_ = self.parse_statements(('name:endif',),
|
||||||
drop_needle=True)
|
drop_needle=True)
|
||||||
else:
|
|
||||||
node.else_ = []
|
|
||||||
break
|
break
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -334,10 +334,9 @@ def parse_context():
|
|||||||
if parse_context() or self.stream.current.type != 'comma':
|
if parse_context() or self.stream.current.type != 'comma':
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
break
|
self.stream.expect('name')
|
||||||
if not hasattr(node, 'with_context'):
|
if not hasattr(node, 'with_context'):
|
||||||
node.with_context = False
|
node.with_context = False
|
||||||
self.stream.skip_if('comma')
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def parse_signature(self, node):
|
def parse_signature(self, node):
|
||||||
@ -395,15 +394,21 @@ def parse_print(self):
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
def parse_assign_target(self, with_tuple=True, name_only=False,
|
def parse_assign_target(self, with_tuple=True, name_only=False,
|
||||||
extra_end_rules=None):
|
extra_end_rules=None, with_namespace=False):
|
||||||
"""Parse an assignment target. As Jinja2 allows assignments to
|
"""Parse an assignment target. As Jinja2 allows assignments to
|
||||||
tuples, this function can parse all allowed assignment targets. Per
|
tuples, this function can parse all allowed assignment targets. Per
|
||||||
default assignments to tuples are parsed, that can be disable however
|
default assignments to tuples are parsed, that can be disable however
|
||||||
by setting `with_tuple` to `False`. If only assignments to names are
|
by setting `with_tuple` to `False`. If only assignments to names are
|
||||||
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
||||||
parameter is forwarded to the tuple parsing function.
|
parameter is forwarded to the tuple parsing function. If
|
||||||
|
`with_namespace` is enabled, a namespace assignment may be parsed.
|
||||||
"""
|
"""
|
||||||
if name_only:
|
if with_namespace and self.stream.look().type == 'dot':
|
||||||
|
token = self.stream.expect('name')
|
||||||
|
next(self.stream) # dot
|
||||||
|
attr = self.stream.expect('name')
|
||||||
|
target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
|
||||||
|
elif name_only:
|
||||||
token = self.stream.expect('name')
|
token = self.stream.expect('name')
|
||||||
target = nodes.Name(token.value, 'store', lineno=token.lineno)
|
target = nodes.Name(token.value, 'store', lineno=token.lineno)
|
||||||
else:
|
else:
|
||||||
|
54
lib/spack/external/jinja2/runtime.py
vendored
54
lib/spack/external/jinja2/runtime.py
vendored
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
from jinja2.nodes import EvalContext, _context_function_types
|
from jinja2.nodes import EvalContext, _context_function_types
|
||||||
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
|
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
|
||||||
internalcode, object_type_repr, evalcontextfunction
|
internalcode, object_type_repr, evalcontextfunction, Namespace
|
||||||
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
||||||
TemplateNotFound
|
TemplateNotFound
|
||||||
from jinja2._compat import imap, text_type, iteritems, \
|
from jinja2._compat import imap, text_type, iteritems, \
|
||||||
@ -27,7 +27,7 @@
|
|||||||
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
|
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
|
||||||
'TemplateRuntimeError', 'missing', 'concat', 'escape',
|
'TemplateRuntimeError', 'missing', 'concat', 'escape',
|
||||||
'markup_join', 'unicode_join', 'to_string', 'identity',
|
'markup_join', 'unicode_join', 'to_string', 'identity',
|
||||||
'TemplateNotFound']
|
'TemplateNotFound', 'Namespace']
|
||||||
|
|
||||||
#: the name of the function that is used to convert something into
|
#: the name of the function that is used to convert something into
|
||||||
#: a string. We can just use the text type here.
|
#: a string. We can just use the text type here.
|
||||||
@ -36,6 +36,7 @@
|
|||||||
#: the identity function. Useful for certain things in the environment
|
#: the identity function. Useful for certain things in the environment
|
||||||
identity = lambda x: x
|
identity = lambda x: x
|
||||||
|
|
||||||
|
_first_iteration = object()
|
||||||
_last_iteration = object()
|
_last_iteration = object()
|
||||||
|
|
||||||
|
|
||||||
@ -241,13 +242,14 @@ def call(__self, __obj, *args, **kwargs):
|
|||||||
__traceback_hide__ = True # noqa
|
__traceback_hide__ = True # noqa
|
||||||
|
|
||||||
# Allow callable classes to take a context
|
# Allow callable classes to take a context
|
||||||
fn = __obj.__call__
|
if hasattr(__obj, '__call__'):
|
||||||
for fn_type in ('contextfunction',
|
fn = __obj.__call__
|
||||||
'evalcontextfunction',
|
for fn_type in ('contextfunction',
|
||||||
'environmentfunction'):
|
'evalcontextfunction',
|
||||||
if hasattr(fn, fn_type):
|
'environmentfunction'):
|
||||||
__obj = fn
|
if hasattr(fn, fn_type):
|
||||||
break
|
__obj = fn
|
||||||
|
break
|
||||||
|
|
||||||
if isinstance(__obj, _context_function_types):
|
if isinstance(__obj, _context_function_types):
|
||||||
if getattr(__obj, 'contextfunction', 0):
|
if getattr(__obj, 'contextfunction', 0):
|
||||||
@ -349,13 +351,17 @@ def __call__(self):
|
|||||||
class LoopContextBase(object):
|
class LoopContextBase(object):
|
||||||
"""A loop context for dynamic iteration."""
|
"""A loop context for dynamic iteration."""
|
||||||
|
|
||||||
|
_before = _first_iteration
|
||||||
|
_current = _first_iteration
|
||||||
_after = _last_iteration
|
_after = _last_iteration
|
||||||
_length = None
|
_length = None
|
||||||
|
|
||||||
def __init__(self, recurse=None, depth0=0):
|
def __init__(self, undefined, recurse=None, depth0=0):
|
||||||
|
self._undefined = undefined
|
||||||
self._recurse = recurse
|
self._recurse = recurse
|
||||||
self.index0 = -1
|
self.index0 = -1
|
||||||
self.depth0 = depth0
|
self.depth0 = depth0
|
||||||
|
self._last_checked_value = missing
|
||||||
|
|
||||||
def cycle(self, *args):
|
def cycle(self, *args):
|
||||||
"""Cycles among the arguments with the current loop index."""
|
"""Cycles among the arguments with the current loop index."""
|
||||||
@ -363,6 +369,13 @@ def cycle(self, *args):
|
|||||||
raise TypeError('no items for cycling given')
|
raise TypeError('no items for cycling given')
|
||||||
return args[self.index0 % len(args)]
|
return args[self.index0 % len(args)]
|
||||||
|
|
||||||
|
def changed(self, *value):
|
||||||
|
"""Checks whether the value has changed since the last call."""
|
||||||
|
if self._last_checked_value != value:
|
||||||
|
self._last_checked_value = value
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
first = property(lambda x: x.index0 == 0)
|
first = property(lambda x: x.index0 == 0)
|
||||||
last = property(lambda x: x._after is _last_iteration)
|
last = property(lambda x: x._after is _last_iteration)
|
||||||
index = property(lambda x: x.index0 + 1)
|
index = property(lambda x: x.index0 + 1)
|
||||||
@ -370,6 +383,18 @@ def cycle(self, *args):
|
|||||||
revindex0 = property(lambda x: x.length - x.index)
|
revindex0 = property(lambda x: x.length - x.index)
|
||||||
depth = property(lambda x: x.depth0 + 1)
|
depth = property(lambda x: x.depth0 + 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def previtem(self):
|
||||||
|
if self._before is _first_iteration:
|
||||||
|
return self._undefined('there is no previous item')
|
||||||
|
return self._before
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nextitem(self):
|
||||||
|
if self._after is _last_iteration:
|
||||||
|
return self._undefined('there is no next item')
|
||||||
|
return self._after
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return self.length
|
return self.length
|
||||||
|
|
||||||
@ -395,8 +420,8 @@ def __repr__(self):
|
|||||||
|
|
||||||
class LoopContext(LoopContextBase):
|
class LoopContext(LoopContextBase):
|
||||||
|
|
||||||
def __init__(self, iterable, recurse=None, depth0=0):
|
def __init__(self, iterable, undefined, recurse=None, depth0=0):
|
||||||
LoopContextBase.__init__(self, recurse, depth0)
|
LoopContextBase.__init__(self, undefined, recurse, depth0)
|
||||||
self._iterator = iter(iterable)
|
self._iterator = iter(iterable)
|
||||||
|
|
||||||
# try to get the length of the iterable early. This must be done
|
# try to get the length of the iterable early. This must be done
|
||||||
@ -448,9 +473,10 @@ def __next__(self):
|
|||||||
ctx.index0 += 1
|
ctx.index0 += 1
|
||||||
if ctx._after is _last_iteration:
|
if ctx._after is _last_iteration:
|
||||||
raise StopIteration()
|
raise StopIteration()
|
||||||
next_elem = ctx._after
|
ctx._before = ctx._current
|
||||||
|
ctx._current = ctx._after
|
||||||
ctx._after = ctx._safe_next()
|
ctx._after = ctx._safe_next()
|
||||||
return next_elem, ctx
|
return ctx._current, ctx
|
||||||
|
|
||||||
|
|
||||||
class Macro(object):
|
class Macro(object):
|
||||||
|
2
lib/spack/external/jinja2/sandbox.py
vendored
2
lib/spack/external/jinja2/sandbox.py
vendored
@ -107,7 +107,7 @@ class _MagicFormatMapping(Mapping):
|
|||||||
"""This class implements a dummy wrapper to fix a bug in the Python
|
"""This class implements a dummy wrapper to fix a bug in the Python
|
||||||
standard library for string formatting.
|
standard library for string formatting.
|
||||||
|
|
||||||
See http://bugs.python.org/issue13598 for information about why
|
See https://bugs.python.org/issue13598 for information about why
|
||||||
this is necessary.
|
this is necessary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
54
lib/spack/external/jinja2/tests.py
vendored
54
lib/spack/external/jinja2/tests.py
vendored
@ -8,6 +8,7 @@
|
|||||||
:copyright: (c) 2017 by the Jinja Team.
|
:copyright: (c) 2017 by the Jinja Team.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
import operator
|
||||||
import re
|
import re
|
||||||
from collections import Mapping
|
from collections import Mapping
|
||||||
from jinja2.runtime import Undefined
|
from jinja2.runtime import Undefined
|
||||||
@ -103,28 +104,6 @@ def test_sequence(value):
|
|||||||
return True
|
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):
|
def test_sameas(value, other):
|
||||||
"""Check if an object points to the same memory address than another
|
"""Check if an object points to the same memory address than another
|
||||||
object:
|
object:
|
||||||
@ -152,14 +131,12 @@ def test_escaped(value):
|
|||||||
return hasattr(value, '__html__')
|
return hasattr(value, '__html__')
|
||||||
|
|
||||||
|
|
||||||
def test_greaterthan(value, other):
|
def test_in(value, seq):
|
||||||
"""Check if value is greater than other."""
|
"""Check if value is in seq.
|
||||||
return value > other
|
|
||||||
|
|
||||||
|
.. versionadded:: 2.10
|
||||||
def test_lessthan(value, other):
|
"""
|
||||||
"""Check if value is less than other."""
|
return value in seq
|
||||||
return value < other
|
|
||||||
|
|
||||||
|
|
||||||
TESTS = {
|
TESTS = {
|
||||||
@ -178,8 +155,21 @@ def test_lessthan(value, other):
|
|||||||
'iterable': test_iterable,
|
'iterable': test_iterable,
|
||||||
'callable': test_callable,
|
'callable': test_callable,
|
||||||
'sameas': test_sameas,
|
'sameas': test_sameas,
|
||||||
'equalto': test_equalto,
|
|
||||||
'escaped': test_escaped,
|
'escaped': test_escaped,
|
||||||
'greaterthan': test_greaterthan,
|
'in': test_in,
|
||||||
'lessthan': test_lessthan
|
'==': operator.eq,
|
||||||
|
'eq': operator.eq,
|
||||||
|
'equalto': operator.eq,
|
||||||
|
'!=': operator.ne,
|
||||||
|
'ne': operator.ne,
|
||||||
|
'>': operator.gt,
|
||||||
|
'gt': operator.gt,
|
||||||
|
'greaterthan': operator.gt,
|
||||||
|
'ge': operator.ge,
|
||||||
|
'>=': operator.ge,
|
||||||
|
'<': operator.lt,
|
||||||
|
'lt': operator.lt,
|
||||||
|
'lessthan': operator.lt,
|
||||||
|
'<=': operator.le,
|
||||||
|
'le': operator.le,
|
||||||
}
|
}
|
||||||
|
25
lib/spack/external/jinja2/utils.py
vendored
25
lib/spack/external/jinja2/utils.py
vendored
@ -567,7 +567,7 @@ def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
|
|||||||
.replace(u'>', u'\\u003e') \
|
.replace(u'>', u'\\u003e') \
|
||||||
.replace(u'&', u'\\u0026') \
|
.replace(u'&', u'\\u0026') \
|
||||||
.replace(u"'", u'\\u0027')
|
.replace(u"'", u'\\u0027')
|
||||||
return rv
|
return Markup(rv)
|
||||||
|
|
||||||
|
|
||||||
@implements_iterator
|
@implements_iterator
|
||||||
@ -612,6 +612,29 @@ def __call__(self):
|
|||||||
return self.sep
|
return self.sep
|
||||||
|
|
||||||
|
|
||||||
|
class Namespace(object):
|
||||||
|
"""A namespace object that can hold arbitrary attributes. It may be
|
||||||
|
initialized from a dictionary or with keyword argments."""
|
||||||
|
|
||||||
|
def __init__(*args, **kwargs):
|
||||||
|
self, args = args[0], args[1:]
|
||||||
|
self.__attrs = dict(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getattribute__(self, name):
|
||||||
|
if name == '_Namespace__attrs':
|
||||||
|
return object.__getattribute__(self, name)
|
||||||
|
try:
|
||||||
|
return self.__attrs[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
def __setitem__(self, name, value):
|
||||||
|
self.__attrs[name] = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Namespace %r>' % self.__attrs
|
||||||
|
|
||||||
|
|
||||||
# does this python version support async for in and async generators?
|
# does this python version support async for in and async generators?
|
||||||
try:
|
try:
|
||||||
exec('async def _():\n async for _ in ():\n yield _')
|
exec('async def _():\n async for _ in ():\n yield _')
|
||||||
|
Loading…
Reference in New Issue
Block a user