add jinja2 as embedded/external dependencies
This commit is contained in:
parent
60b9f21491
commit
e8cca46c33
23 changed files with 10596 additions and 0 deletions
69
tools/external/jinja2/__init__.py
vendored
Normal file
69
tools/external/jinja2/__init__.py
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
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 Jinja2 template::
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
__version__ = '2.7.3'
|
||||||
|
|
||||||
|
# high level interface
|
||||||
|
from jinja2.environment import Environment, Template
|
||||||
|
|
||||||
|
# loaders
|
||||||
|
from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
|
||||||
|
DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
|
||||||
|
ModuleLoader
|
||||||
|
|
||||||
|
# bytecode caches
|
||||||
|
from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
|
||||||
|
MemcachedBytecodeCache
|
||||||
|
|
||||||
|
# undefined types
|
||||||
|
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
|
||||||
|
|
||||||
|
# exceptions
|
||||||
|
from jinja2.exceptions import TemplateError, UndefinedError, \
|
||||||
|
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
|
||||||
|
TemplateAssertionError
|
||||||
|
|
||||||
|
# decorators and public utilities
|
||||||
|
from jinja2.filters import environmentfilter, contextfilter, \
|
||||||
|
evalcontextfilter
|
||||||
|
from jinja2.utils import Markup, escape, clear_caches, \
|
||||||
|
environmentfunction, evalcontextfunction, contextfunction, \
|
||||||
|
is_undefined
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
|
||||||
|
'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
|
||||||
|
'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
|
||||||
|
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
|
||||||
|
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
|
||||||
|
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
|
||||||
|
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
|
||||||
|
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
|
||||||
|
'evalcontextfilter', 'evalcontextfunction'
|
||||||
|
]
|
150
tools/external/jinja2/_compat.py
vendored
Normal file
150
tools/external/jinja2/_compat.py
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2._compat
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some py2/py3 compatibility support based on a stripped down
|
||||||
|
version of six so we don't have to depend on a specific version
|
||||||
|
of it.
|
||||||
|
|
||||||
|
:copyright: Copyright 2013 by the Jinja team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
PYPY = hasattr(sys, 'pypy_translation_info')
|
||||||
|
_identity = lambda x: x
|
||||||
|
|
||||||
|
|
||||||
|
if not PY2:
|
||||||
|
unichr = chr
|
||||||
|
range_type = range
|
||||||
|
text_type = str
|
||||||
|
string_types = (str,)
|
||||||
|
|
||||||
|
iterkeys = lambda d: iter(d.keys())
|
||||||
|
itervalues = lambda d: iter(d.values())
|
||||||
|
iteritems = lambda d: iter(d.items())
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
from io import BytesIO, StringIO
|
||||||
|
NativeStringIO = StringIO
|
||||||
|
|
||||||
|
def reraise(tp, value, tb=None):
|
||||||
|
if value.__traceback__ is not tb:
|
||||||
|
raise value.with_traceback(tb)
|
||||||
|
raise value
|
||||||
|
|
||||||
|
ifilter = filter
|
||||||
|
imap = map
|
||||||
|
izip = zip
|
||||||
|
intern = sys.intern
|
||||||
|
|
||||||
|
implements_iterator = _identity
|
||||||
|
implements_to_string = _identity
|
||||||
|
encode_filename = _identity
|
||||||
|
get_next = lambda x: x.__next__
|
||||||
|
|
||||||
|
else:
|
||||||
|
unichr = unichr
|
||||||
|
text_type = unicode
|
||||||
|
range_type = xrange
|
||||||
|
string_types = (str, unicode)
|
||||||
|
|
||||||
|
iterkeys = lambda d: d.iterkeys()
|
||||||
|
itervalues = lambda d: d.itervalues()
|
||||||
|
iteritems = lambda d: d.iteritems()
|
||||||
|
|
||||||
|
import cPickle as pickle
|
||||||
|
from cStringIO import StringIO as BytesIO, StringIO
|
||||||
|
NativeStringIO = BytesIO
|
||||||
|
|
||||||
|
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
|
||||||
|
|
||||||
|
from itertools import imap, izip, ifilter
|
||||||
|
intern = intern
|
||||||
|
|
||||||
|
def implements_iterator(cls):
|
||||||
|
cls.next = cls.__next__
|
||||||
|
del cls.__next__
|
||||||
|
return cls
|
||||||
|
|
||||||
|
def implements_to_string(cls):
|
||||||
|
cls.__unicode__ = cls.__str__
|
||||||
|
cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
|
||||||
|
return cls
|
||||||
|
|
||||||
|
get_next = lambda x: x.next
|
||||||
|
|
||||||
|
def encode_filename(filename):
|
||||||
|
if isinstance(filename, unicode):
|
||||||
|
return filename.encode('utf-8')
|
||||||
|
return filename
|
||||||
|
|
||||||
|
try:
|
||||||
|
next = next
|
||||||
|
except NameError:
|
||||||
|
def next(it):
|
||||||
|
return it.next()
|
||||||
|
|
||||||
|
|
||||||
|
def with_metaclass(meta, *bases):
|
||||||
|
# This requires a bit of explanation: the basic idea is to make a
|
||||||
|
# dummy metaclass for one level of class instanciation that replaces
|
||||||
|
# itself with the actual metaclass. Because of internal type checks
|
||||||
|
# we also need to make sure that we downgrade the custom metaclass
|
||||||
|
# for one level to something closer to type (that's why __call__ and
|
||||||
|
# __init__ comes back from type etc.).
|
||||||
|
#
|
||||||
|
# This has the advantage over six.with_metaclass in that it does not
|
||||||
|
# introduce dummy classes into the final MRO.
|
||||||
|
class metaclass(meta):
|
||||||
|
__call__ = type.__call__
|
||||||
|
__init__ = type.__init__
|
||||||
|
def __new__(cls, name, this_bases, d):
|
||||||
|
if this_bases is None:
|
||||||
|
return type.__new__(cls, name, (), d)
|
||||||
|
return meta(name, bases, d)
|
||||||
|
return metaclass('temporary_class', None, {})
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from collections import Mapping as mapping_types
|
||||||
|
except ImportError:
|
||||||
|
import UserDict
|
||||||
|
mapping_types = (UserDict.UserDict, UserDict.DictMixin, dict)
|
||||||
|
|
||||||
|
|
||||||
|
# common types. These do exist in the special types module too which however
|
||||||
|
# does not exist in IronPython out of the box. Also that way we don't have
|
||||||
|
# to deal with implementation specific stuff here
|
||||||
|
class _C(object):
|
||||||
|
def method(self): pass
|
||||||
|
def _func():
|
||||||
|
yield None
|
||||||
|
function_type = type(_func)
|
||||||
|
generator_type = type(_func())
|
||||||
|
method_type = type(_C().method)
|
||||||
|
code_type = type(_C.method.__code__)
|
||||||
|
try:
|
||||||
|
raise TypeError()
|
||||||
|
except TypeError:
|
||||||
|
_tb = sys.exc_info()[2]
|
||||||
|
traceback_type = type(_tb)
|
||||||
|
frame_type = type(_tb.tb_frame)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib.parse import quote_from_bytes as url_quote
|
||||||
|
except ImportError:
|
||||||
|
from urllib import quote as url_quote
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from thread import allocate_lock
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
from threading import Lock as allocate_lock
|
||||||
|
except ImportError:
|
||||||
|
from dummy_thread import allocate_lock
|
132
tools/external/jinja2/_stringdefs.py
vendored
Normal file
132
tools/external/jinja2/_stringdefs.py
vendored
Normal file
File diff suppressed because one or more lines are too long
344
tools/external/jinja2/bccache.py
vendored
Normal file
344
tools/external/jinja2/bccache.py
vendored
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.bccache
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module implements the bytecode cache system Jinja is optionally
|
||||||
|
using. This is useful if you have very complex template situations and
|
||||||
|
the compiliation of all those templates slow down your application too
|
||||||
|
much.
|
||||||
|
|
||||||
|
Situations where this is useful are often forking web applications that
|
||||||
|
are initialized on the first request.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD.
|
||||||
|
"""
|
||||||
|
from os import path, listdir
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
import errno
|
||||||
|
import marshal
|
||||||
|
import tempfile
|
||||||
|
import fnmatch
|
||||||
|
from hashlib import sha1
|
||||||
|
from jinja2.utils import open_if_exists
|
||||||
|
from jinja2._compat import BytesIO, pickle, PY2, text_type
|
||||||
|
|
||||||
|
|
||||||
|
# marshal works better on 3.x, one hack less required
|
||||||
|
if not PY2:
|
||||||
|
marshal_dump = marshal.dump
|
||||||
|
marshal_load = marshal.load
|
||||||
|
else:
|
||||||
|
|
||||||
|
def marshal_dump(code, f):
|
||||||
|
if isinstance(f, file):
|
||||||
|
marshal.dump(code, f)
|
||||||
|
else:
|
||||||
|
f.write(marshal.dumps(code))
|
||||||
|
|
||||||
|
def marshal_load(f):
|
||||||
|
if isinstance(f, file):
|
||||||
|
return marshal.load(f)
|
||||||
|
return marshal.loads(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
bc_version = 2
|
||||||
|
|
||||||
|
# magic version used to only change with new jinja versions. With 2.6
|
||||||
|
# we change this to also take Python version changes into account. The
|
||||||
|
# reason for this is that Python tends to segfault if fed earlier bytecode
|
||||||
|
# versions because someone thought it would be a good idea to reuse opcodes
|
||||||
|
# or make Python incompatible with earlier versions.
|
||||||
|
bc_magic = 'j2'.encode('ascii') + \
|
||||||
|
pickle.dumps(bc_version, 2) + \
|
||||||
|
pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1])
|
||||||
|
|
||||||
|
|
||||||
|
class Bucket(object):
|
||||||
|
"""Buckets are used to store the bytecode for one template. It's created
|
||||||
|
and initialized by the bytecode cache and passed to the loading functions.
|
||||||
|
|
||||||
|
The buckets get an internal checksum from the cache assigned and use this
|
||||||
|
to automatically reject outdated cache material. Individual bytecode
|
||||||
|
cache subclasses don't have to care about cache invalidation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, environment, key, checksum):
|
||||||
|
self.environment = environment
|
||||||
|
self.key = key
|
||||||
|
self.checksum = checksum
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Resets the bucket (unloads the bytecode)."""
|
||||||
|
self.code = None
|
||||||
|
|
||||||
|
def load_bytecode(self, f):
|
||||||
|
"""Loads bytecode from a file or file like object."""
|
||||||
|
# make sure the magic header is correct
|
||||||
|
magic = f.read(len(bc_magic))
|
||||||
|
if magic != bc_magic:
|
||||||
|
self.reset()
|
||||||
|
return
|
||||||
|
# the source code of the file changed, we need to reload
|
||||||
|
checksum = pickle.load(f)
|
||||||
|
if self.checksum != checksum:
|
||||||
|
self.reset()
|
||||||
|
return
|
||||||
|
self.code = marshal_load(f)
|
||||||
|
|
||||||
|
def write_bytecode(self, f):
|
||||||
|
"""Dump the bytecode into the file or file like object passed."""
|
||||||
|
if self.code is None:
|
||||||
|
raise TypeError('can\'t write empty bucket')
|
||||||
|
f.write(bc_magic)
|
||||||
|
pickle.dump(self.checksum, f, 2)
|
||||||
|
marshal_dump(self.code, f)
|
||||||
|
|
||||||
|
def bytecode_from_string(self, string):
|
||||||
|
"""Load bytecode from a string."""
|
||||||
|
self.load_bytecode(BytesIO(string))
|
||||||
|
|
||||||
|
def bytecode_to_string(self):
|
||||||
|
"""Return the bytecode as string."""
|
||||||
|
out = BytesIO()
|
||||||
|
self.write_bytecode(out)
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
class BytecodeCache(object):
|
||||||
|
"""To implement your own bytecode cache you have to subclass this class
|
||||||
|
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
|
||||||
|
these methods are passed a :class:`~jinja2.bccache.Bucket`.
|
||||||
|
|
||||||
|
A very basic bytecode cache that saves the bytecode on the file system::
|
||||||
|
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
class MyCache(BytecodeCache):
|
||||||
|
|
||||||
|
def __init__(self, directory):
|
||||||
|
self.directory = directory
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket):
|
||||||
|
filename = path.join(self.directory, bucket.key)
|
||||||
|
if path.exists(filename):
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
bucket.load_bytecode(f)
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket):
|
||||||
|
filename = path.join(self.directory, bucket.key)
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
bucket.write_bytecode(f)
|
||||||
|
|
||||||
|
A more advanced version of a filesystem based bytecode cache is part of
|
||||||
|
Jinja2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket):
|
||||||
|
"""Subclasses have to override this method to load bytecode into a
|
||||||
|
bucket. If they are not able to find code in the cache for the
|
||||||
|
bucket, it must not do anything.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket):
|
||||||
|
"""Subclasses have to override this method to write the bytecode
|
||||||
|
from a bucket back to the cache. If it unable to do so it must not
|
||||||
|
fail silently but raise an exception.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Clears the cache. This method is not used by Jinja2 but should be
|
||||||
|
implemented to allow applications to clear the bytecode cache used
|
||||||
|
by a particular environment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_cache_key(self, name, filename=None):
|
||||||
|
"""Returns the unique hash key for this template name."""
|
||||||
|
hash = sha1(name.encode('utf-8'))
|
||||||
|
if filename is not None:
|
||||||
|
filename = '|' + filename
|
||||||
|
if isinstance(filename, text_type):
|
||||||
|
filename = filename.encode('utf-8')
|
||||||
|
hash.update(filename)
|
||||||
|
return hash.hexdigest()
|
||||||
|
|
||||||
|
def get_source_checksum(self, source):
|
||||||
|
"""Returns a checksum for the source."""
|
||||||
|
return sha1(source.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def get_bucket(self, environment, name, filename, source):
|
||||||
|
"""Return a cache bucket for the given template. All arguments are
|
||||||
|
mandatory but filename may be `None`.
|
||||||
|
"""
|
||||||
|
key = self.get_cache_key(name, filename)
|
||||||
|
checksum = self.get_source_checksum(source)
|
||||||
|
bucket = Bucket(environment, key, checksum)
|
||||||
|
self.load_bytecode(bucket)
|
||||||
|
return bucket
|
||||||
|
|
||||||
|
def set_bucket(self, bucket):
|
||||||
|
"""Put the bucket into the cache."""
|
||||||
|
self.dump_bytecode(bucket)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemBytecodeCache(BytecodeCache):
|
||||||
|
"""A bytecode cache that stores bytecode on the filesystem. It accepts
|
||||||
|
two arguments: The directory where the cache items are stored and a
|
||||||
|
pattern string that is used to build the filename.
|
||||||
|
|
||||||
|
If no directory is specified a default cache directory is selected. On
|
||||||
|
Windows the user's temp directory is used, on UNIX systems a directory
|
||||||
|
is created for the user in the system temp directory.
|
||||||
|
|
||||||
|
The pattern can be used to have multiple separate caches operate on the
|
||||||
|
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
|
||||||
|
is replaced with the cache key.
|
||||||
|
|
||||||
|
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
|
||||||
|
|
||||||
|
This bytecode cache supports clearing of the cache using the clear method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
|
||||||
|
if directory is None:
|
||||||
|
directory = self._get_default_cache_dir()
|
||||||
|
self.directory = directory
|
||||||
|
self.pattern = pattern
|
||||||
|
|
||||||
|
def _get_default_cache_dir(self):
|
||||||
|
tmpdir = tempfile.gettempdir()
|
||||||
|
|
||||||
|
# On windows the temporary directory is used specific unless
|
||||||
|
# explicitly forced otherwise. We can just use that.
|
||||||
|
if os.name == 'nt':
|
||||||
|
return tmpdir
|
||||||
|
if not hasattr(os, 'getuid'):
|
||||||
|
raise RuntimeError('Cannot determine safe temp directory. You '
|
||||||
|
'need to explicitly provide one.')
|
||||||
|
|
||||||
|
dirname = '_jinja2-cache-%d' % os.getuid()
|
||||||
|
actual_dir = os.path.join(tmpdir, dirname)
|
||||||
|
try:
|
||||||
|
os.mkdir(actual_dir, stat.S_IRWXU) # 0o700
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EEXIST:
|
||||||
|
raise
|
||||||
|
|
||||||
|
actual_dir_stat = os.lstat(actual_dir)
|
||||||
|
if actual_dir_stat.st_uid != os.getuid() \
|
||||||
|
or not stat.S_ISDIR(actual_dir_stat.st_mode) \
|
||||||
|
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
|
||||||
|
raise RuntimeError('Temporary directory \'%s\' has an incorrect '
|
||||||
|
'owner, permissions, or type.' % actual_dir)
|
||||||
|
|
||||||
|
return actual_dir
|
||||||
|
|
||||||
|
def _get_cache_filename(self, bucket):
|
||||||
|
return path.join(self.directory, self.pattern % bucket.key)
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket):
|
||||||
|
f = open_if_exists(self._get_cache_filename(bucket), 'rb')
|
||||||
|
if f is not None:
|
||||||
|
try:
|
||||||
|
bucket.load_bytecode(f)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket):
|
||||||
|
f = open(self._get_cache_filename(bucket), 'wb')
|
||||||
|
try:
|
||||||
|
bucket.write_bytecode(f)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
# imported lazily here because google app-engine doesn't support
|
||||||
|
# write access on the file system and the function does not exist
|
||||||
|
# normally.
|
||||||
|
from os import remove
|
||||||
|
files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
|
||||||
|
for filename in files:
|
||||||
|
try:
|
||||||
|
remove(path.join(self.directory, filename))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcachedBytecodeCache(BytecodeCache):
|
||||||
|
"""This class implements a bytecode cache that uses a memcache cache for
|
||||||
|
storing the information. It does not enforce a specific memcache library
|
||||||
|
(tummy's memcache or cmemcache) but will accept any class that provides
|
||||||
|
the minimal interface required.
|
||||||
|
|
||||||
|
Libraries compatible with this class:
|
||||||
|
|
||||||
|
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
|
||||||
|
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
|
||||||
|
- `cmemcache <http://gijsbert.org/cmemcache/>`_
|
||||||
|
|
||||||
|
(Unfortunately the django cache interface is not compatible because it
|
||||||
|
does not support storing binary data, only unicode. You can however pass
|
||||||
|
the underlying cache client to the bytecode cache which is available
|
||||||
|
as `django.core.cache.cache._client`.)
|
||||||
|
|
||||||
|
The minimal interface for the client passed to the constructor is this:
|
||||||
|
|
||||||
|
.. class:: MinimalClientInterface
|
||||||
|
|
||||||
|
.. method:: set(key, value[, timeout])
|
||||||
|
|
||||||
|
Stores the bytecode in the cache. `value` is a string and
|
||||||
|
`timeout` the timeout of the key. If timeout is not provided
|
||||||
|
a default timeout or no timeout should be assumed, if it's
|
||||||
|
provided it's an integer with the number of seconds the cache
|
||||||
|
item should exist.
|
||||||
|
|
||||||
|
.. method:: get(key)
|
||||||
|
|
||||||
|
Returns the value for the cache key. If the item does not
|
||||||
|
exist in the cache the return value must be `None`.
|
||||||
|
|
||||||
|
The other arguments to the constructor are the prefix for all keys that
|
||||||
|
is added before the actual cache key and the timeout for the bytecode in
|
||||||
|
the cache system. We recommend a high (or no) timeout.
|
||||||
|
|
||||||
|
This bytecode cache does not support clearing of used items in the cache.
|
||||||
|
The clear method is a no-operation function.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
Added support for ignoring memcache errors through the
|
||||||
|
`ignore_memcache_errors` parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, client, prefix='jinja2/bytecode/', timeout=None,
|
||||||
|
ignore_memcache_errors=True):
|
||||||
|
self.client = client
|
||||||
|
self.prefix = prefix
|
||||||
|
self.timeout = timeout
|
||||||
|
self.ignore_memcache_errors = ignore_memcache_errors
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket):
|
||||||
|
try:
|
||||||
|
code = self.client.get(self.prefix + bucket.key)
|
||||||
|
except Exception:
|
||||||
|
if not self.ignore_memcache_errors:
|
||||||
|
raise
|
||||||
|
code = None
|
||||||
|
if code is not None:
|
||||||
|
bucket.bytecode_from_string(code)
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket):
|
||||||
|
args = (self.prefix + bucket.key, bucket.bytecode_to_string())
|
||||||
|
if self.timeout is not None:
|
||||||
|
args += (self.timeout,)
|
||||||
|
try:
|
||||||
|
self.client.set(*args)
|
||||||
|
except Exception:
|
||||||
|
if not self.ignore_memcache_errors:
|
||||||
|
raise
|
1640
tools/external/jinja2/compiler.py
vendored
Normal file
1640
tools/external/jinja2/compiler.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
32
tools/external/jinja2/constants.py
vendored
Normal file
32
tools/external/jinja2/constants.py
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja.constants
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Various constants.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
#: list of lorem ipsum words used by the lipsum() helper function
|
||||||
|
LOREM_IPSUM_WORDS = u'''\
|
||||||
|
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
|
||||||
|
auctor augue bibendum blandit class commodo condimentum congue consectetuer
|
||||||
|
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
|
||||||
|
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
|
||||||
|
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
|
||||||
|
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
|
||||||
|
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
|
||||||
|
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
|
||||||
|
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
|
||||||
|
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
|
||||||
|
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
|
||||||
|
penatibus per pharetra phasellus placerat platea porta porttitor posuere
|
||||||
|
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
|
||||||
|
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
|
||||||
|
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
|
||||||
|
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
|
||||||
|
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
|
||||||
|
viverra volutpat vulputate'''
|
337
tools/external/jinja2/debug.py
vendored
Normal file
337
tools/external/jinja2/debug.py
vendored
Normal file
|
@ -0,0 +1,337 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.debug
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the debug interface for Jinja. This module does some pretty
|
||||||
|
ugly stuff with the Python traceback system in order to achieve tracebacks
|
||||||
|
with correct line numbers, locals and contents.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from types import TracebackType
|
||||||
|
from jinja2.utils import missing, internal_code
|
||||||
|
from jinja2.exceptions import TemplateSyntaxError
|
||||||
|
from jinja2._compat import iteritems, reraise, code_type
|
||||||
|
|
||||||
|
# on pypy we can take advantage of transparent proxies
|
||||||
|
try:
|
||||||
|
from __pypy__ import tproxy
|
||||||
|
except ImportError:
|
||||||
|
tproxy = None
|
||||||
|
|
||||||
|
|
||||||
|
# how does the raise helper look like?
|
||||||
|
try:
|
||||||
|
exec("raise TypeError, 'foo'")
|
||||||
|
except SyntaxError:
|
||||||
|
raise_helper = 'raise __jinja_exception__[1]'
|
||||||
|
except TypeError:
|
||||||
|
raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
|
||||||
|
|
||||||
|
|
||||||
|
class TracebackFrameProxy(object):
|
||||||
|
"""Proxies a traceback frame."""
|
||||||
|
|
||||||
|
def __init__(self, tb):
|
||||||
|
self.tb = tb
|
||||||
|
self._tb_next = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tb_next(self):
|
||||||
|
return self._tb_next
|
||||||
|
|
||||||
|
def set_next(self, next):
|
||||||
|
if tb_set_next is not None:
|
||||||
|
try:
|
||||||
|
tb_set_next(self.tb, next and next.tb or None)
|
||||||
|
except Exception:
|
||||||
|
# this function can fail due to all the hackery it does
|
||||||
|
# on various python implementations. We just catch errors
|
||||||
|
# down and ignore them if necessary.
|
||||||
|
pass
|
||||||
|
self._tb_next = next
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_jinja_frame(self):
|
||||||
|
return '__jinja_template__' in self.tb.tb_frame.f_globals
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self.tb, name)
|
||||||
|
|
||||||
|
|
||||||
|
def make_frame_proxy(frame):
|
||||||
|
proxy = TracebackFrameProxy(frame)
|
||||||
|
if tproxy is None:
|
||||||
|
return proxy
|
||||||
|
def operation_handler(operation, *args, **kwargs):
|
||||||
|
if operation in ('__getattribute__', '__getattr__'):
|
||||||
|
return getattr(proxy, args[0])
|
||||||
|
elif operation == '__setattr__':
|
||||||
|
proxy.__setattr__(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return getattr(proxy, operation)(*args, **kwargs)
|
||||||
|
return tproxy(TracebackType, operation_handler)
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessedTraceback(object):
|
||||||
|
"""Holds a Jinja preprocessed traceback for printing or reraising."""
|
||||||
|
|
||||||
|
def __init__(self, exc_type, exc_value, frames):
|
||||||
|
assert frames, 'no frames for this traceback?'
|
||||||
|
self.exc_type = exc_type
|
||||||
|
self.exc_value = exc_value
|
||||||
|
self.frames = frames
|
||||||
|
|
||||||
|
# newly concatenate the frames (which are proxies)
|
||||||
|
prev_tb = None
|
||||||
|
for tb in self.frames:
|
||||||
|
if prev_tb is not None:
|
||||||
|
prev_tb.set_next(tb)
|
||||||
|
prev_tb = tb
|
||||||
|
prev_tb.set_next(None)
|
||||||
|
|
||||||
|
def render_as_text(self, limit=None):
|
||||||
|
"""Return a string with the traceback."""
|
||||||
|
lines = traceback.format_exception(self.exc_type, self.exc_value,
|
||||||
|
self.frames[0], limit=limit)
|
||||||
|
return ''.join(lines).rstrip()
|
||||||
|
|
||||||
|
def render_as_html(self, full=False):
|
||||||
|
"""Return a unicode string with the traceback as rendered HTML."""
|
||||||
|
from jinja2.debugrenderer import render_traceback
|
||||||
|
return u'%s\n\n<!--\n%s\n-->' % (
|
||||||
|
render_traceback(self, full=full),
|
||||||
|
self.render_as_text().decode('utf-8', 'replace')
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_template_syntax_error(self):
|
||||||
|
"""`True` if this is a template syntax error."""
|
||||||
|
return isinstance(self.exc_value, TemplateSyntaxError)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exc_info(self):
|
||||||
|
"""Exception info tuple with a proxy around the frame objects."""
|
||||||
|
return self.exc_type, self.exc_value, self.frames[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def standard_exc_info(self):
|
||||||
|
"""Standard python exc_info for re-raising"""
|
||||||
|
tb = self.frames[0]
|
||||||
|
# the frame will be an actual traceback (or transparent proxy) if
|
||||||
|
# we are on pypy or a python implementation with support for tproxy
|
||||||
|
if type(tb) is not TracebackType:
|
||||||
|
tb = tb.tb
|
||||||
|
return self.exc_type, self.exc_value, tb
|
||||||
|
|
||||||
|
|
||||||
|
def make_traceback(exc_info, source_hint=None):
|
||||||
|
"""Creates a processed traceback object from the exc_info."""
|
||||||
|
exc_type, exc_value, tb = exc_info
|
||||||
|
if isinstance(exc_value, TemplateSyntaxError):
|
||||||
|
exc_info = translate_syntax_error(exc_value, source_hint)
|
||||||
|
initial_skip = 0
|
||||||
|
else:
|
||||||
|
initial_skip = 1
|
||||||
|
return translate_exception(exc_info, initial_skip)
|
||||||
|
|
||||||
|
|
||||||
|
def translate_syntax_error(error, source=None):
|
||||||
|
"""Rewrites a syntax error to please traceback systems."""
|
||||||
|
error.source = source
|
||||||
|
error.translated = True
|
||||||
|
exc_info = (error.__class__, error, None)
|
||||||
|
filename = error.filename
|
||||||
|
if filename is None:
|
||||||
|
filename = '<unknown>'
|
||||||
|
return fake_exc_info(exc_info, filename, error.lineno)
|
||||||
|
|
||||||
|
|
||||||
|
def translate_exception(exc_info, initial_skip=0):
|
||||||
|
"""If passed an exc_info it will automatically rewrite the exceptions
|
||||||
|
all the way down to the correct line numbers and frames.
|
||||||
|
"""
|
||||||
|
tb = exc_info[2]
|
||||||
|
frames = []
|
||||||
|
|
||||||
|
# skip some internal frames if wanted
|
||||||
|
for x in range(initial_skip):
|
||||||
|
if tb is not None:
|
||||||
|
tb = tb.tb_next
|
||||||
|
initial_tb = tb
|
||||||
|
|
||||||
|
while tb is not None:
|
||||||
|
# skip frames decorated with @internalcode. These are internal
|
||||||
|
# calls we can't avoid and that are useless in template debugging
|
||||||
|
# output.
|
||||||
|
if tb.tb_frame.f_code in internal_code:
|
||||||
|
tb = tb.tb_next
|
||||||
|
continue
|
||||||
|
|
||||||
|
# save a reference to the next frame if we override the current
|
||||||
|
# one with a faked one.
|
||||||
|
next = tb.tb_next
|
||||||
|
|
||||||
|
# fake template exceptions
|
||||||
|
template = tb.tb_frame.f_globals.get('__jinja_template__')
|
||||||
|
if template is not None:
|
||||||
|
lineno = template.get_corresponding_lineno(tb.tb_lineno)
|
||||||
|
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
|
||||||
|
lineno)[2]
|
||||||
|
|
||||||
|
frames.append(make_frame_proxy(tb))
|
||||||
|
tb = next
|
||||||
|
|
||||||
|
# if we don't have any exceptions in the frames left, we have to
|
||||||
|
# reraise it unchanged.
|
||||||
|
# XXX: can we backup here? when could this happen?
|
||||||
|
if not frames:
|
||||||
|
reraise(exc_info[0], exc_info[1], exc_info[2])
|
||||||
|
|
||||||
|
return ProcessedTraceback(exc_info[0], exc_info[1], frames)
|
||||||
|
|
||||||
|
|
||||||
|
def fake_exc_info(exc_info, filename, lineno):
|
||||||
|
"""Helper for `translate_exception`."""
|
||||||
|
exc_type, exc_value, tb = exc_info
|
||||||
|
|
||||||
|
# figure the real context out
|
||||||
|
if tb is not None:
|
||||||
|
real_locals = tb.tb_frame.f_locals.copy()
|
||||||
|
ctx = real_locals.get('context')
|
||||||
|
if ctx:
|
||||||
|
locals = ctx.get_all()
|
||||||
|
else:
|
||||||
|
locals = {}
|
||||||
|
for name, value in iteritems(real_locals):
|
||||||
|
if name.startswith('l_') and value is not missing:
|
||||||
|
locals[name[2:]] = value
|
||||||
|
|
||||||
|
# if there is a local called __jinja_exception__, we get
|
||||||
|
# rid of it to not break the debug functionality.
|
||||||
|
locals.pop('__jinja_exception__', None)
|
||||||
|
else:
|
||||||
|
locals = {}
|
||||||
|
|
||||||
|
# assamble fake globals we need
|
||||||
|
globals = {
|
||||||
|
'__name__': filename,
|
||||||
|
'__file__': filename,
|
||||||
|
'__jinja_exception__': exc_info[:2],
|
||||||
|
|
||||||
|
# we don't want to keep the reference to the template around
|
||||||
|
# to not cause circular dependencies, but we mark it as Jinja
|
||||||
|
# frame for the ProcessedTraceback
|
||||||
|
'__jinja_template__': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# and fake the exception
|
||||||
|
code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
|
||||||
|
|
||||||
|
# if it's possible, change the name of the code. This won't work
|
||||||
|
# on some python environments such as google appengine
|
||||||
|
try:
|
||||||
|
if tb is None:
|
||||||
|
location = 'template'
|
||||||
|
else:
|
||||||
|
function = tb.tb_frame.f_code.co_name
|
||||||
|
if function == 'root':
|
||||||
|
location = 'top-level template code'
|
||||||
|
elif function.startswith('block_'):
|
||||||
|
location = 'block "%s"' % function[6:]
|
||||||
|
else:
|
||||||
|
location = 'template'
|
||||||
|
code = code_type(0, code.co_nlocals, code.co_stacksize,
|
||||||
|
code.co_flags, code.co_code, code.co_consts,
|
||||||
|
code.co_names, code.co_varnames, filename,
|
||||||
|
location, code.co_firstlineno,
|
||||||
|
code.co_lnotab, (), ())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# execute the code and catch the new traceback
|
||||||
|
try:
|
||||||
|
exec(code, globals, locals)
|
||||||
|
except:
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
new_tb = exc_info[2].tb_next
|
||||||
|
|
||||||
|
# return without this frame
|
||||||
|
return exc_info[:2] + (new_tb,)
|
||||||
|
|
||||||
|
|
||||||
|
def _init_ugly_crap():
|
||||||
|
"""This function implements a few ugly things so that we can patch the
|
||||||
|
traceback objects. The function returned allows resetting `tb_next` on
|
||||||
|
any python traceback object. Do not attempt to use this on non cpython
|
||||||
|
interpreters
|
||||||
|
"""
|
||||||
|
import ctypes
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
|
# figure out side of _Py_ssize_t
|
||||||
|
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
|
||||||
|
_Py_ssize_t = ctypes.c_int64
|
||||||
|
else:
|
||||||
|
_Py_ssize_t = ctypes.c_int
|
||||||
|
|
||||||
|
# regular python
|
||||||
|
class _PyObject(ctypes.Structure):
|
||||||
|
pass
|
||||||
|
_PyObject._fields_ = [
|
||||||
|
('ob_refcnt', _Py_ssize_t),
|
||||||
|
('ob_type', ctypes.POINTER(_PyObject))
|
||||||
|
]
|
||||||
|
|
||||||
|
# python with trace
|
||||||
|
if hasattr(sys, 'getobjects'):
|
||||||
|
class _PyObject(ctypes.Structure):
|
||||||
|
pass
|
||||||
|
_PyObject._fields_ = [
|
||||||
|
('_ob_next', ctypes.POINTER(_PyObject)),
|
||||||
|
('_ob_prev', ctypes.POINTER(_PyObject)),
|
||||||
|
('ob_refcnt', _Py_ssize_t),
|
||||||
|
('ob_type', ctypes.POINTER(_PyObject))
|
||||||
|
]
|
||||||
|
|
||||||
|
class _Traceback(_PyObject):
|
||||||
|
pass
|
||||||
|
_Traceback._fields_ = [
|
||||||
|
('tb_next', ctypes.POINTER(_Traceback)),
|
||||||
|
('tb_frame', ctypes.POINTER(_PyObject)),
|
||||||
|
('tb_lasti', ctypes.c_int),
|
||||||
|
('tb_lineno', ctypes.c_int)
|
||||||
|
]
|
||||||
|
|
||||||
|
def tb_set_next(tb, next):
|
||||||
|
"""Set the tb_next attribute of a traceback object."""
|
||||||
|
if not (isinstance(tb, TracebackType) and
|
||||||
|
(next is None or isinstance(next, TracebackType))):
|
||||||
|
raise TypeError('tb_set_next arguments must be traceback objects')
|
||||||
|
obj = _Traceback.from_address(id(tb))
|
||||||
|
if tb.tb_next is not None:
|
||||||
|
old = _Traceback.from_address(id(tb.tb_next))
|
||||||
|
old.ob_refcnt -= 1
|
||||||
|
if next is None:
|
||||||
|
obj.tb_next = ctypes.POINTER(_Traceback)()
|
||||||
|
else:
|
||||||
|
next = _Traceback.from_address(id(next))
|
||||||
|
next.ob_refcnt += 1
|
||||||
|
obj.tb_next = ctypes.pointer(next)
|
||||||
|
|
||||||
|
return tb_set_next
|
||||||
|
|
||||||
|
|
||||||
|
# try to get a tb_set_next implementation if we don't have transparent
|
||||||
|
# proxies.
|
||||||
|
tb_set_next = None
|
||||||
|
if tproxy is None:
|
||||||
|
try:
|
||||||
|
tb_set_next = _init_ugly_crap()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
del _init_ugly_crap
|
43
tools/external/jinja2/defaults.py
vendored
Normal file
43
tools/external/jinja2/defaults.py
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.defaults
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Jinja default filters and tags.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from jinja2._compat import range_type
|
||||||
|
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
|
||||||
|
|
||||||
|
|
||||||
|
# defaults for the parser / lexer
|
||||||
|
BLOCK_START_STRING = '{%'
|
||||||
|
BLOCK_END_STRING = '%}'
|
||||||
|
VARIABLE_START_STRING = '{{'
|
||||||
|
VARIABLE_END_STRING = '}}'
|
||||||
|
COMMENT_START_STRING = '{#'
|
||||||
|
COMMENT_END_STRING = '#}'
|
||||||
|
LINE_STATEMENT_PREFIX = None
|
||||||
|
LINE_COMMENT_PREFIX = None
|
||||||
|
TRIM_BLOCKS = False
|
||||||
|
LSTRIP_BLOCKS = False
|
||||||
|
NEWLINE_SEQUENCE = '\n'
|
||||||
|
KEEP_TRAILING_NEWLINE = False
|
||||||
|
|
||||||
|
|
||||||
|
# default filters, tests and namespace
|
||||||
|
from jinja2.filters import FILTERS as DEFAULT_FILTERS
|
||||||
|
from jinja2.tests import TESTS as DEFAULT_TESTS
|
||||||
|
DEFAULT_NAMESPACE = {
|
||||||
|
'range': range_type,
|
||||||
|
'dict': lambda **kw: kw,
|
||||||
|
'lipsum': generate_lorem_ipsum,
|
||||||
|
'cycler': Cycler,
|
||||||
|
'joiner': Joiner
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# export all constants
|
||||||
|
__all__ = tuple(x for x in locals().keys() if x.isupper())
|
1191
tools/external/jinja2/environment.py
vendored
Normal file
1191
tools/external/jinja2/environment.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
146
tools/external/jinja2/exceptions.py
vendored
Normal file
146
tools/external/jinja2/exceptions.py
vendored
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.exceptions
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Jinja exceptions.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from jinja2._compat import imap, text_type, PY2, implements_to_string
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateError(Exception):
|
||||||
|
"""Baseclass for all template errors."""
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
def __init__(self, message=None):
|
||||||
|
if message is not None:
|
||||||
|
message = text_type(message).encode('utf-8')
|
||||||
|
Exception.__init__(self, message)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
if self.args:
|
||||||
|
message = self.args[0]
|
||||||
|
if message is not None:
|
||||||
|
return message.decode('utf-8', 'replace')
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.message or u''
|
||||||
|
else:
|
||||||
|
def __init__(self, message=None):
|
||||||
|
Exception.__init__(self, message)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
if self.args:
|
||||||
|
message = self.args[0]
|
||||||
|
if message is not None:
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
@implements_to_string
|
||||||
|
class TemplateNotFound(IOError, LookupError, TemplateError):
|
||||||
|
"""Raised if a template does not exist."""
|
||||||
|
|
||||||
|
# looks weird, but removes the warning descriptor that just
|
||||||
|
# bogusly warns us about message being deprecated
|
||||||
|
message = None
|
||||||
|
|
||||||
|
def __init__(self, name, message=None):
|
||||||
|
IOError.__init__(self)
|
||||||
|
if message is None:
|
||||||
|
message = name
|
||||||
|
self.message = message
|
||||||
|
self.name = name
|
||||||
|
self.templates = [name]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
|
class TemplatesNotFound(TemplateNotFound):
|
||||||
|
"""Like :class:`TemplateNotFound` but raised if multiple templates
|
||||||
|
are selected. This is a subclass of :class:`TemplateNotFound`
|
||||||
|
exception, so just catching the base exception will catch both.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, names=(), message=None):
|
||||||
|
if message is None:
|
||||||
|
message = u'none of the templates given were found: ' + \
|
||||||
|
u', '.join(imap(text_type, names))
|
||||||
|
TemplateNotFound.__init__(self, names and names[-1] or None, message)
|
||||||
|
self.templates = list(names)
|
||||||
|
|
||||||
|
|
||||||
|
@implements_to_string
|
||||||
|
class TemplateSyntaxError(TemplateError):
|
||||||
|
"""Raised to tell the user that there is a problem with the template."""
|
||||||
|
|
||||||
|
def __init__(self, message, lineno, name=None, filename=None):
|
||||||
|
TemplateError.__init__(self, message)
|
||||||
|
self.lineno = lineno
|
||||||
|
self.name = name
|
||||||
|
self.filename = filename
|
||||||
|
self.source = None
|
||||||
|
|
||||||
|
# this is set to True if the debug.translate_syntax_error
|
||||||
|
# function translated the syntax error into a new traceback
|
||||||
|
self.translated = False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# for translated errors we only return the message
|
||||||
|
if self.translated:
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
# otherwise attach some stuff
|
||||||
|
location = 'line %d' % self.lineno
|
||||||
|
name = self.filename or self.name
|
||||||
|
if name:
|
||||||
|
location = 'File "%s", %s' % (name, location)
|
||||||
|
lines = [self.message, ' ' + location]
|
||||||
|
|
||||||
|
# if the source is set, add the line to the output
|
||||||
|
if self.source is not None:
|
||||||
|
try:
|
||||||
|
line = self.source.splitlines()[self.lineno - 1]
|
||||||
|
except IndexError:
|
||||||
|
line = None
|
||||||
|
if line:
|
||||||
|
lines.append(' ' + line.strip())
|
||||||
|
|
||||||
|
return u'\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateAssertionError(TemplateSyntaxError):
|
||||||
|
"""Like a template syntax error, but covers cases where something in the
|
||||||
|
template caused an error at compile time that wasn't necessarily caused
|
||||||
|
by a syntax error. However it's a direct subclass of
|
||||||
|
:exc:`TemplateSyntaxError` and has the same attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateRuntimeError(TemplateError):
|
||||||
|
"""A generic runtime error in the template engine. Under some situations
|
||||||
|
Jinja may raise this exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class UndefinedError(TemplateRuntimeError):
|
||||||
|
"""Raised if a template tries to operate on :class:`Undefined`."""
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityError(TemplateRuntimeError):
|
||||||
|
"""Raised if a template tries to do something insecure if the
|
||||||
|
sandbox is enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class FilterArgumentError(TemplateRuntimeError):
|
||||||
|
"""This error is raised if a filter was called with inappropriate
|
||||||
|
arguments
|
||||||
|
"""
|
636
tools/external/jinja2/ext.py
vendored
Normal file
636
tools/external/jinja2/ext.py
vendored
Normal file
|
@ -0,0 +1,636 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.ext
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Jinja extensions allow to add custom tags similar to the way django custom
|
||||||
|
tags work. By default two example extensions exist: an i18n and a cache
|
||||||
|
extension.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD.
|
||||||
|
"""
|
||||||
|
from jinja2 import nodes
|
||||||
|
from jinja2.defaults import BLOCK_START_STRING, \
|
||||||
|
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
|
||||||
|
COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
|
||||||
|
LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
|
||||||
|
KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
|
||||||
|
from jinja2.environment import Environment
|
||||||
|
from jinja2.runtime import concat
|
||||||
|
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
|
||||||
|
from jinja2.utils import contextfunction, import_string, Markup
|
||||||
|
from jinja2._compat import next, with_metaclass, string_types, iteritems
|
||||||
|
|
||||||
|
|
||||||
|
# the only real useful gettext functions for a Jinja template. Note
|
||||||
|
# that ugettext must be assigned to gettext as Jinja doesn't support
|
||||||
|
# non unicode strings.
|
||||||
|
GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionRegistry(type):
|
||||||
|
"""Gives the extension an unique identifier."""
|
||||||
|
|
||||||
|
def __new__(cls, name, bases, d):
|
||||||
|
rv = type.__new__(cls, name, bases, d)
|
||||||
|
rv.identifier = rv.__module__ + '.' + rv.__name__
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class Extension(with_metaclass(ExtensionRegistry, object)):
|
||||||
|
"""Extensions can be used to add extra functionality to the Jinja template
|
||||||
|
system at the parser level. Custom extensions are bound to an environment
|
||||||
|
but may not store environment specific data on `self`. The reason for
|
||||||
|
this is that an extension can be bound to another environment (for
|
||||||
|
overlays) by creating a copy and reassigning the `environment` attribute.
|
||||||
|
|
||||||
|
As extensions are created by the environment they cannot accept any
|
||||||
|
arguments for configuration. One may want to work around that by using
|
||||||
|
a factory function, but that is not possible as extensions are identified
|
||||||
|
by their import name. The correct way to configure the extension is
|
||||||
|
storing the configuration values on the environment. Because this way the
|
||||||
|
environment ends up acting as central configuration storage the
|
||||||
|
attributes may clash which is why extensions have to ensure that the names
|
||||||
|
they choose for configuration are not too generic. ``prefix`` for example
|
||||||
|
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
|
||||||
|
name as includes the name of the extension (fragment cache).
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: if this extension parses this is the list of tags it's listening to.
|
||||||
|
tags = set()
|
||||||
|
|
||||||
|
#: the priority of that extension. This is especially useful for
|
||||||
|
#: extensions that preprocess values. A lower value means higher
|
||||||
|
#: priority.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.4
|
||||||
|
priority = 100
|
||||||
|
|
||||||
|
def __init__(self, environment):
|
||||||
|
self.environment = environment
|
||||||
|
|
||||||
|
def bind(self, environment):
|
||||||
|
"""Create a copy of this extension bound to another environment."""
|
||||||
|
rv = object.__new__(self.__class__)
|
||||||
|
rv.__dict__.update(self.__dict__)
|
||||||
|
rv.environment = environment
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def preprocess(self, source, name, filename=None):
|
||||||
|
"""This method is called before the actual lexing and can be used to
|
||||||
|
preprocess the source. The `filename` is optional. The return value
|
||||||
|
must be the preprocessed source.
|
||||||
|
"""
|
||||||
|
return source
|
||||||
|
|
||||||
|
def filter_stream(self, stream):
|
||||||
|
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
|
||||||
|
to filter tokens returned. This method has to return an iterable of
|
||||||
|
:class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
|
||||||
|
:class:`~jinja2.lexer.TokenStream`.
|
||||||
|
|
||||||
|
In the `ext` folder of the Jinja2 source distribution there is a file
|
||||||
|
called `inlinegettext.py` which implements a filter that utilizes this
|
||||||
|
method.
|
||||||
|
"""
|
||||||
|
return stream
|
||||||
|
|
||||||
|
def parse(self, parser):
|
||||||
|
"""If any of the :attr:`tags` matched this method is called with the
|
||||||
|
parser as first argument. The token the parser stream is pointing at
|
||||||
|
is the name token that matched. This method has to return one or a
|
||||||
|
list of multiple nodes.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def attr(self, name, lineno=None):
|
||||||
|
"""Return an attribute node for the current extension. This is useful
|
||||||
|
to pass constants on extensions to generated template code.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
self.attr('_my_attribute', lineno=lineno)
|
||||||
|
"""
|
||||||
|
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
|
||||||
|
|
||||||
|
def call_method(self, name, args=None, kwargs=None, dyn_args=None,
|
||||||
|
dyn_kwargs=None, lineno=None):
|
||||||
|
"""Call a method of the extension. This is a shortcut for
|
||||||
|
:meth:`attr` + :class:`jinja2.nodes.Call`.
|
||||||
|
"""
|
||||||
|
if args is None:
|
||||||
|
args = []
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = []
|
||||||
|
return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
|
||||||
|
dyn_args, dyn_kwargs, lineno=lineno)
|
||||||
|
|
||||||
|
|
||||||
|
@contextfunction
|
||||||
|
def _gettext_alias(__context, *args, **kwargs):
|
||||||
|
return __context.call(__context.resolve('gettext'), *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_new_gettext(func):
|
||||||
|
@contextfunction
|
||||||
|
def gettext(__context, __string, **variables):
|
||||||
|
rv = __context.call(func, __string)
|
||||||
|
if __context.eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
return rv % variables
|
||||||
|
return gettext
|
||||||
|
|
||||||
|
|
||||||
|
def _make_new_ngettext(func):
|
||||||
|
@contextfunction
|
||||||
|
def ngettext(__context, __singular, __plural, __num, **variables):
|
||||||
|
variables.setdefault('num', __num)
|
||||||
|
rv = __context.call(func, __singular, __plural, __num)
|
||||||
|
if __context.eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
return rv % variables
|
||||||
|
return ngettext
|
||||||
|
|
||||||
|
|
||||||
|
class InternationalizationExtension(Extension):
|
||||||
|
"""This extension adds gettext support to Jinja2."""
|
||||||
|
tags = set(['trans'])
|
||||||
|
|
||||||
|
# TODO: the i18n extension is currently reevaluating values in a few
|
||||||
|
# situations. Take this example:
|
||||||
|
# {% trans count=something() %}{{ count }} foo{% pluralize
|
||||||
|
# %}{{ count }} fooss{% endtrans %}
|
||||||
|
# something is called twice here. One time for the gettext value and
|
||||||
|
# the other time for the n-parameter of the ngettext function.
|
||||||
|
|
||||||
|
def __init__(self, environment):
|
||||||
|
Extension.__init__(self, environment)
|
||||||
|
environment.globals['_'] = _gettext_alias
|
||||||
|
environment.extend(
|
||||||
|
install_gettext_translations=self._install,
|
||||||
|
install_null_translations=self._install_null,
|
||||||
|
install_gettext_callables=self._install_callables,
|
||||||
|
uninstall_gettext_translations=self._uninstall,
|
||||||
|
extract_translations=self._extract,
|
||||||
|
newstyle_gettext=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def _install(self, translations, newstyle=None):
|
||||||
|
gettext = getattr(translations, 'ugettext', None)
|
||||||
|
if gettext is None:
|
||||||
|
gettext = translations.gettext
|
||||||
|
ngettext = getattr(translations, 'ungettext', None)
|
||||||
|
if ngettext is None:
|
||||||
|
ngettext = translations.ngettext
|
||||||
|
self._install_callables(gettext, ngettext, newstyle)
|
||||||
|
|
||||||
|
def _install_null(self, newstyle=None):
|
||||||
|
self._install_callables(
|
||||||
|
lambda x: x,
|
||||||
|
lambda s, p, n: (n != 1 and (p,) or (s,))[0],
|
||||||
|
newstyle
|
||||||
|
)
|
||||||
|
|
||||||
|
def _install_callables(self, gettext, ngettext, newstyle=None):
|
||||||
|
if newstyle is not None:
|
||||||
|
self.environment.newstyle_gettext = newstyle
|
||||||
|
if self.environment.newstyle_gettext:
|
||||||
|
gettext = _make_new_gettext(gettext)
|
||||||
|
ngettext = _make_new_ngettext(ngettext)
|
||||||
|
self.environment.globals.update(
|
||||||
|
gettext=gettext,
|
||||||
|
ngettext=ngettext
|
||||||
|
)
|
||||||
|
|
||||||
|
def _uninstall(self, translations):
|
||||||
|
for key in 'gettext', 'ngettext':
|
||||||
|
self.environment.globals.pop(key, None)
|
||||||
|
|
||||||
|
def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
|
||||||
|
if isinstance(source, string_types):
|
||||||
|
source = self.environment.parse(source)
|
||||||
|
return extract_from_ast(source, gettext_functions)
|
||||||
|
|
||||||
|
def parse(self, parser):
|
||||||
|
"""Parse a translatable tag."""
|
||||||
|
lineno = next(parser.stream).lineno
|
||||||
|
num_called_num = False
|
||||||
|
|
||||||
|
# find all the variables referenced. Additionally a variable can be
|
||||||
|
# defined in the body of the trans block too, but this is checked at
|
||||||
|
# a later state.
|
||||||
|
plural_expr = None
|
||||||
|
plural_expr_assignment = None
|
||||||
|
variables = {}
|
||||||
|
while parser.stream.current.type != 'block_end':
|
||||||
|
if variables:
|
||||||
|
parser.stream.expect('comma')
|
||||||
|
|
||||||
|
# skip colon for python compatibility
|
||||||
|
if parser.stream.skip_if('colon'):
|
||||||
|
break
|
||||||
|
|
||||||
|
name = parser.stream.expect('name')
|
||||||
|
if name.value in variables:
|
||||||
|
parser.fail('translatable variable %r defined twice.' %
|
||||||
|
name.value, name.lineno,
|
||||||
|
exc=TemplateAssertionError)
|
||||||
|
|
||||||
|
# expressions
|
||||||
|
if parser.stream.current.type == 'assign':
|
||||||
|
next(parser.stream)
|
||||||
|
variables[name.value] = var = parser.parse_expression()
|
||||||
|
else:
|
||||||
|
variables[name.value] = var = nodes.Name(name.value, 'load')
|
||||||
|
|
||||||
|
if plural_expr is None:
|
||||||
|
if isinstance(var, nodes.Call):
|
||||||
|
plural_expr = nodes.Name('_trans', 'load')
|
||||||
|
variables[name.value] = plural_expr
|
||||||
|
plural_expr_assignment = nodes.Assign(
|
||||||
|
nodes.Name('_trans', 'store'), var)
|
||||||
|
else:
|
||||||
|
plural_expr = var
|
||||||
|
num_called_num = name.value == 'num'
|
||||||
|
|
||||||
|
parser.stream.expect('block_end')
|
||||||
|
|
||||||
|
plural = plural_names = None
|
||||||
|
have_plural = False
|
||||||
|
referenced = set()
|
||||||
|
|
||||||
|
# now parse until endtrans or pluralize
|
||||||
|
singular_names, singular = self._parse_block(parser, True)
|
||||||
|
if singular_names:
|
||||||
|
referenced.update(singular_names)
|
||||||
|
if plural_expr is None:
|
||||||
|
plural_expr = nodes.Name(singular_names[0], 'load')
|
||||||
|
num_called_num = singular_names[0] == 'num'
|
||||||
|
|
||||||
|
# if we have a pluralize block, we parse that too
|
||||||
|
if parser.stream.current.test('name:pluralize'):
|
||||||
|
have_plural = True
|
||||||
|
next(parser.stream)
|
||||||
|
if parser.stream.current.type != 'block_end':
|
||||||
|
name = parser.stream.expect('name')
|
||||||
|
if name.value not in variables:
|
||||||
|
parser.fail('unknown variable %r for pluralization' %
|
||||||
|
name.value, name.lineno,
|
||||||
|
exc=TemplateAssertionError)
|
||||||
|
plural_expr = variables[name.value]
|
||||||
|
num_called_num = name.value == 'num'
|
||||||
|
parser.stream.expect('block_end')
|
||||||
|
plural_names, plural = self._parse_block(parser, False)
|
||||||
|
next(parser.stream)
|
||||||
|
referenced.update(plural_names)
|
||||||
|
else:
|
||||||
|
next(parser.stream)
|
||||||
|
|
||||||
|
# register free names as simple name expressions
|
||||||
|
for var in referenced:
|
||||||
|
if var not in variables:
|
||||||
|
variables[var] = nodes.Name(var, 'load')
|
||||||
|
|
||||||
|
if not have_plural:
|
||||||
|
plural_expr = None
|
||||||
|
elif plural_expr is None:
|
||||||
|
parser.fail('pluralize without variables', lineno)
|
||||||
|
|
||||||
|
node = self._make_node(singular, plural, variables, plural_expr,
|
||||||
|
bool(referenced),
|
||||||
|
num_called_num and have_plural)
|
||||||
|
node.set_lineno(lineno)
|
||||||
|
if plural_expr_assignment is not None:
|
||||||
|
return [plural_expr_assignment, node]
|
||||||
|
else:
|
||||||
|
return node
|
||||||
|
|
||||||
|
def _parse_block(self, parser, allow_pluralize):
|
||||||
|
"""Parse until the next block tag with a given name."""
|
||||||
|
referenced = []
|
||||||
|
buf = []
|
||||||
|
while 1:
|
||||||
|
if parser.stream.current.type == 'data':
|
||||||
|
buf.append(parser.stream.current.value.replace('%', '%%'))
|
||||||
|
next(parser.stream)
|
||||||
|
elif parser.stream.current.type == 'variable_begin':
|
||||||
|
next(parser.stream)
|
||||||
|
name = parser.stream.expect('name').value
|
||||||
|
referenced.append(name)
|
||||||
|
buf.append('%%(%s)s' % name)
|
||||||
|
parser.stream.expect('variable_end')
|
||||||
|
elif parser.stream.current.type == 'block_begin':
|
||||||
|
next(parser.stream)
|
||||||
|
if parser.stream.current.test('name:endtrans'):
|
||||||
|
break
|
||||||
|
elif parser.stream.current.test('name:pluralize'):
|
||||||
|
if allow_pluralize:
|
||||||
|
break
|
||||||
|
parser.fail('a translatable section can have only one '
|
||||||
|
'pluralize section')
|
||||||
|
parser.fail('control structures in translatable sections are '
|
||||||
|
'not allowed')
|
||||||
|
elif parser.stream.eos:
|
||||||
|
parser.fail('unclosed translation block')
|
||||||
|
else:
|
||||||
|
assert False, 'internal parser error'
|
||||||
|
|
||||||
|
return referenced, concat(buf)
|
||||||
|
|
||||||
|
def _make_node(self, singular, plural, variables, plural_expr,
|
||||||
|
vars_referenced, num_called_num):
|
||||||
|
"""Generates a useful node from the data provided."""
|
||||||
|
# no variables referenced? no need to escape for old style
|
||||||
|
# gettext invocations only if there are vars.
|
||||||
|
if not vars_referenced and not self.environment.newstyle_gettext:
|
||||||
|
singular = singular.replace('%%', '%')
|
||||||
|
if plural:
|
||||||
|
plural = plural.replace('%%', '%')
|
||||||
|
|
||||||
|
# singular only:
|
||||||
|
if plural_expr is None:
|
||||||
|
gettext = nodes.Name('gettext', 'load')
|
||||||
|
node = nodes.Call(gettext, [nodes.Const(singular)],
|
||||||
|
[], None, None)
|
||||||
|
|
||||||
|
# singular and plural
|
||||||
|
else:
|
||||||
|
ngettext = nodes.Name('ngettext', 'load')
|
||||||
|
node = nodes.Call(ngettext, [
|
||||||
|
nodes.Const(singular),
|
||||||
|
nodes.Const(plural),
|
||||||
|
plural_expr
|
||||||
|
], [], None, None)
|
||||||
|
|
||||||
|
# in case newstyle gettext is used, the method is powerful
|
||||||
|
# enough to handle the variable expansion and autoescape
|
||||||
|
# handling itself
|
||||||
|
if self.environment.newstyle_gettext:
|
||||||
|
for key, value in iteritems(variables):
|
||||||
|
# the function adds that later anyways in case num was
|
||||||
|
# called num, so just skip it.
|
||||||
|
if num_called_num and key == 'num':
|
||||||
|
continue
|
||||||
|
node.kwargs.append(nodes.Keyword(key, value))
|
||||||
|
|
||||||
|
# otherwise do that here
|
||||||
|
else:
|
||||||
|
# mark the return value as safe if we are in an
|
||||||
|
# environment with autoescaping turned on
|
||||||
|
node = nodes.MarkSafeIfAutoescape(node)
|
||||||
|
if variables:
|
||||||
|
node = nodes.Mod(node, nodes.Dict([
|
||||||
|
nodes.Pair(nodes.Const(key), value)
|
||||||
|
for key, value in variables.items()
|
||||||
|
]))
|
||||||
|
return nodes.Output([node])
|
||||||
|
|
||||||
|
|
||||||
|
class ExprStmtExtension(Extension):
|
||||||
|
"""Adds a `do` tag to Jinja2 that works like the print statement just
|
||||||
|
that it doesn't print the return value.
|
||||||
|
"""
|
||||||
|
tags = set(['do'])
|
||||||
|
|
||||||
|
def parse(self, parser):
|
||||||
|
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
|
||||||
|
node.node = parser.parse_tuple()
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
class LoopControlExtension(Extension):
|
||||||
|
"""Adds break and continue to the template engine."""
|
||||||
|
tags = set(['break', 'continue'])
|
||||||
|
|
||||||
|
def parse(self, parser):
|
||||||
|
token = next(parser.stream)
|
||||||
|
if token.value == 'break':
|
||||||
|
return nodes.Break(lineno=token.lineno)
|
||||||
|
return nodes.Continue(lineno=token.lineno)
|
||||||
|
|
||||||
|
|
||||||
|
class WithExtension(Extension):
|
||||||
|
"""Adds support for a django-like with block."""
|
||||||
|
tags = set(['with'])
|
||||||
|
|
||||||
|
def parse(self, parser):
|
||||||
|
node = nodes.Scope(lineno=next(parser.stream).lineno)
|
||||||
|
assignments = []
|
||||||
|
while parser.stream.current.type != 'block_end':
|
||||||
|
lineno = parser.stream.current.lineno
|
||||||
|
if assignments:
|
||||||
|
parser.stream.expect('comma')
|
||||||
|
target = parser.parse_assign_target()
|
||||||
|
parser.stream.expect('assign')
|
||||||
|
expr = parser.parse_expression()
|
||||||
|
assignments.append(nodes.Assign(target, expr, lineno=lineno))
|
||||||
|
node.body = assignments + \
|
||||||
|
list(parser.parse_statements(('name:endwith',),
|
||||||
|
drop_needle=True))
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
class AutoEscapeExtension(Extension):
|
||||||
|
"""Changes auto escape rules for a scope."""
|
||||||
|
tags = set(['autoescape'])
|
||||||
|
|
||||||
|
def parse(self, parser):
|
||||||
|
node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
|
||||||
|
node.options = [
|
||||||
|
nodes.Keyword('autoescape', parser.parse_expression())
|
||||||
|
]
|
||||||
|
node.body = parser.parse_statements(('name:endautoescape',),
|
||||||
|
drop_needle=True)
|
||||||
|
return nodes.Scope([node])
|
||||||
|
|
||||||
|
|
||||||
|
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
|
||||||
|
babel_style=True):
|
||||||
|
"""Extract localizable strings from the given template node. Per
|
||||||
|
default this function returns matches in babel style that means non string
|
||||||
|
parameters as well as keyword arguments are returned as `None`. This
|
||||||
|
allows Babel to figure out what you really meant if you are using
|
||||||
|
gettext functions that allow keyword arguments for placeholder expansion.
|
||||||
|
If you don't want that behavior set the `babel_style` parameter to `False`
|
||||||
|
which causes only strings to be returned and parameters are always stored
|
||||||
|
in tuples. As a consequence invalid gettext calls (calls without a single
|
||||||
|
string parameter or string parameters after non-string parameters) are
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
This example explains the behavior:
|
||||||
|
|
||||||
|
>>> from jinja2 import Environment
|
||||||
|
>>> env = Environment()
|
||||||
|
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
|
||||||
|
>>> list(extract_from_ast(node))
|
||||||
|
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
|
||||||
|
>>> list(extract_from_ast(node, babel_style=False))
|
||||||
|
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
|
||||||
|
|
||||||
|
For every string found this function yields a ``(lineno, function,
|
||||||
|
message)`` tuple, where:
|
||||||
|
|
||||||
|
* ``lineno`` is the number of the line on which the string was found,
|
||||||
|
* ``function`` is the name of the ``gettext`` function used (if the
|
||||||
|
string was extracted from embedded Python code), and
|
||||||
|
* ``message`` is the string itself (a ``unicode`` object, or a tuple
|
||||||
|
of ``unicode`` objects for functions with multiple string arguments).
|
||||||
|
|
||||||
|
This extraction function operates on the AST and is because of that unable
|
||||||
|
to extract any comments. For comment support you have to use the babel
|
||||||
|
extraction interface or extract comments yourself.
|
||||||
|
"""
|
||||||
|
for node in node.find_all(nodes.Call):
|
||||||
|
if not isinstance(node.node, nodes.Name) or \
|
||||||
|
node.node.name not in gettext_functions:
|
||||||
|
continue
|
||||||
|
|
||||||
|
strings = []
|
||||||
|
for arg in node.args:
|
||||||
|
if isinstance(arg, nodes.Const) and \
|
||||||
|
isinstance(arg.value, string_types):
|
||||||
|
strings.append(arg.value)
|
||||||
|
else:
|
||||||
|
strings.append(None)
|
||||||
|
|
||||||
|
for arg in node.kwargs:
|
||||||
|
strings.append(None)
|
||||||
|
if node.dyn_args is not None:
|
||||||
|
strings.append(None)
|
||||||
|
if node.dyn_kwargs is not None:
|
||||||
|
strings.append(None)
|
||||||
|
|
||||||
|
if not babel_style:
|
||||||
|
strings = tuple(x for x in strings if x is not None)
|
||||||
|
if not strings:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if len(strings) == 1:
|
||||||
|
strings = strings[0]
|
||||||
|
else:
|
||||||
|
strings = tuple(strings)
|
||||||
|
yield node.lineno, node.node.name, strings
|
||||||
|
|
||||||
|
|
||||||
|
class _CommentFinder(object):
|
||||||
|
"""Helper class to find comments in a token stream. Can only
|
||||||
|
find comments for gettext calls forwards. Once the comment
|
||||||
|
from line 4 is found, a comment for line 1 will not return a
|
||||||
|
usable value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, tokens, comment_tags):
|
||||||
|
self.tokens = tokens
|
||||||
|
self.comment_tags = comment_tags
|
||||||
|
self.offset = 0
|
||||||
|
self.last_lineno = 0
|
||||||
|
|
||||||
|
def find_backwards(self, offset):
|
||||||
|
try:
|
||||||
|
for _, token_type, token_value in \
|
||||||
|
reversed(self.tokens[self.offset:offset]):
|
||||||
|
if token_type in ('comment', 'linecomment'):
|
||||||
|
try:
|
||||||
|
prefix, comment = token_value.split(None, 1)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if prefix in self.comment_tags:
|
||||||
|
return [comment.rstrip()]
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
self.offset = offset
|
||||||
|
|
||||||
|
def find_comments(self, lineno):
|
||||||
|
if not self.comment_tags or self.last_lineno > lineno:
|
||||||
|
return []
|
||||||
|
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
|
||||||
|
if token_lineno > lineno:
|
||||||
|
return self.find_backwards(self.offset + idx)
|
||||||
|
return self.find_backwards(len(self.tokens))
|
||||||
|
|
||||||
|
|
||||||
|
def babel_extract(fileobj, keywords, comment_tags, options):
|
||||||
|
"""Babel extraction method for Jinja templates.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.3
|
||||||
|
Basic support for translation comments was added. If `comment_tags`
|
||||||
|
is now set to a list of keywords for extraction, the extractor will
|
||||||
|
try to find the best preceeding comment that begins with one of the
|
||||||
|
keywords. For best results, make sure to not have more than one
|
||||||
|
gettext call in one line of code and the matching comment in the
|
||||||
|
same line or the line before.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.5.1
|
||||||
|
The `newstyle_gettext` flag can be set to `True` to enable newstyle
|
||||||
|
gettext calls.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
A `silent` option can now be provided. If set to `False` template
|
||||||
|
syntax errors are propagated instead of being ignored.
|
||||||
|
|
||||||
|
:param fileobj: the file-like object the messages should be extracted from
|
||||||
|
:param keywords: a list of keywords (i.e. function names) that should be
|
||||||
|
recognized as translation functions
|
||||||
|
:param comment_tags: a list of translator tags to search for and include
|
||||||
|
in the results.
|
||||||
|
:param options: a dictionary of additional options (optional)
|
||||||
|
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
|
||||||
|
(comments will be empty currently)
|
||||||
|
"""
|
||||||
|
extensions = set()
|
||||||
|
for extension in options.get('extensions', '').split(','):
|
||||||
|
extension = extension.strip()
|
||||||
|
if not extension:
|
||||||
|
continue
|
||||||
|
extensions.add(import_string(extension))
|
||||||
|
if InternationalizationExtension not in extensions:
|
||||||
|
extensions.add(InternationalizationExtension)
|
||||||
|
|
||||||
|
def getbool(options, key, default=False):
|
||||||
|
return options.get(key, str(default)).lower() in \
|
||||||
|
('1', 'on', 'yes', 'true')
|
||||||
|
|
||||||
|
silent = getbool(options, 'silent', True)
|
||||||
|
environment = Environment(
|
||||||
|
options.get('block_start_string', BLOCK_START_STRING),
|
||||||
|
options.get('block_end_string', BLOCK_END_STRING),
|
||||||
|
options.get('variable_start_string', VARIABLE_START_STRING),
|
||||||
|
options.get('variable_end_string', VARIABLE_END_STRING),
|
||||||
|
options.get('comment_start_string', COMMENT_START_STRING),
|
||||||
|
options.get('comment_end_string', COMMENT_END_STRING),
|
||||||
|
options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
|
||||||
|
options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
|
||||||
|
getbool(options, 'trim_blocks', TRIM_BLOCKS),
|
||||||
|
getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS),
|
||||||
|
NEWLINE_SEQUENCE,
|
||||||
|
getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
|
||||||
|
frozenset(extensions),
|
||||||
|
cache_size=0,
|
||||||
|
auto_reload=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if getbool(options, 'newstyle_gettext'):
|
||||||
|
environment.newstyle_gettext = True
|
||||||
|
|
||||||
|
source = fileobj.read().decode(options.get('encoding', 'utf-8'))
|
||||||
|
try:
|
||||||
|
node = environment.parse(source)
|
||||||
|
tokens = list(environment.lex(environment.preprocess(source)))
|
||||||
|
except TemplateSyntaxError as e:
|
||||||
|
if not silent:
|
||||||
|
raise
|
||||||
|
# skip templates with syntax errors
|
||||||
|
return
|
||||||
|
|
||||||
|
finder = _CommentFinder(tokens, comment_tags)
|
||||||
|
for lineno, func, message in extract_from_ast(node, keywords):
|
||||||
|
yield lineno, func, message, finder.find_comments(lineno)
|
||||||
|
|
||||||
|
|
||||||
|
#: nicer import names
|
||||||
|
i18n = InternationalizationExtension
|
||||||
|
do = ExprStmtExtension
|
||||||
|
loopcontrols = LoopControlExtension
|
||||||
|
with_ = WithExtension
|
||||||
|
autoescape = AutoEscapeExtension
|
987
tools/external/jinja2/filters.py
vendored
Normal file
987
tools/external/jinja2/filters.py
vendored
Normal file
|
@ -0,0 +1,987 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.filters
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Bundled jinja filters.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import math
|
||||||
|
|
||||||
|
from random import choice
|
||||||
|
from operator import itemgetter
|
||||||
|
from itertools import groupby
|
||||||
|
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
|
||||||
|
unicode_urlencode
|
||||||
|
from jinja2.runtime import Undefined
|
||||||
|
from jinja2.exceptions import FilterArgumentError
|
||||||
|
from jinja2._compat import next, imap, string_types, text_type, iteritems
|
||||||
|
|
||||||
|
|
||||||
|
_word_re = re.compile(r'\w+(?u)')
|
||||||
|
|
||||||
|
|
||||||
|
def contextfilter(f):
|
||||||
|
"""Decorator for marking context dependent filters. The current
|
||||||
|
:class:`Context` will be passed as first argument.
|
||||||
|
"""
|
||||||
|
f.contextfilter = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def evalcontextfilter(f):
|
||||||
|
"""Decorator for marking eval-context dependent filters. An eval
|
||||||
|
context object is passed as first argument. For more information
|
||||||
|
about the eval context, see :ref:`eval-context`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.4
|
||||||
|
"""
|
||||||
|
f.evalcontextfilter = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def environmentfilter(f):
|
||||||
|
"""Decorator for marking evironment dependent filters. The current
|
||||||
|
:class:`Environment` is passed to the filter as first argument.
|
||||||
|
"""
|
||||||
|
f.environmentfilter = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def make_attrgetter(environment, attribute):
|
||||||
|
"""Returns a callable that looks up the given attribute from a
|
||||||
|
passed object with the rules of the environment. Dots are allowed
|
||||||
|
to access attributes of attributes. Integer parts in paths are
|
||||||
|
looked up as integers.
|
||||||
|
"""
|
||||||
|
if not isinstance(attribute, string_types) \
|
||||||
|
or ('.' not in attribute and not attribute.isdigit()):
|
||||||
|
return lambda x: environment.getitem(x, attribute)
|
||||||
|
attribute = attribute.split('.')
|
||||||
|
def attrgetter(item):
|
||||||
|
for part in attribute:
|
||||||
|
if part.isdigit():
|
||||||
|
part = int(part)
|
||||||
|
item = environment.getitem(item, part)
|
||||||
|
return item
|
||||||
|
return attrgetter
|
||||||
|
|
||||||
|
|
||||||
|
def do_forceescape(value):
|
||||||
|
"""Enforce HTML escaping. This will probably double escape variables."""
|
||||||
|
if hasattr(value, '__html__'):
|
||||||
|
value = value.__html__()
|
||||||
|
return escape(text_type(value))
|
||||||
|
|
||||||
|
|
||||||
|
def do_urlencode(value):
|
||||||
|
"""Escape strings for use in URLs (uses UTF-8 encoding). It accepts both
|
||||||
|
dictionaries and regular strings as well as pairwise iterables.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
"""
|
||||||
|
itemiter = None
|
||||||
|
if isinstance(value, dict):
|
||||||
|
itemiter = iteritems(value)
|
||||||
|
elif not isinstance(value, string_types):
|
||||||
|
try:
|
||||||
|
itemiter = iter(value)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
if itemiter is None:
|
||||||
|
return unicode_urlencode(value)
|
||||||
|
return u'&'.join(unicode_urlencode(k) + '=' +
|
||||||
|
unicode_urlencode(v) for k, v in itemiter)
|
||||||
|
|
||||||
|
|
||||||
|
@evalcontextfilter
|
||||||
|
def do_replace(eval_ctx, s, old, new, count=None):
|
||||||
|
"""Return a copy of the value with all occurrences of a substring
|
||||||
|
replaced with a new one. The first argument is the substring
|
||||||
|
that should be replaced, the second is the replacement string.
|
||||||
|
If the optional third argument ``count`` is given, only the first
|
||||||
|
``count`` occurrences are replaced:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ "Hello World"|replace("Hello", "Goodbye") }}
|
||||||
|
-> Goodbye World
|
||||||
|
|
||||||
|
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
|
||||||
|
-> d'oh, d'oh, aaargh
|
||||||
|
"""
|
||||||
|
if count is None:
|
||||||
|
count = -1
|
||||||
|
if not eval_ctx.autoescape:
|
||||||
|
return text_type(s).replace(text_type(old), text_type(new), count)
|
||||||
|
if hasattr(old, '__html__') or hasattr(new, '__html__') and \
|
||||||
|
not hasattr(s, '__html__'):
|
||||||
|
s = escape(s)
|
||||||
|
else:
|
||||||
|
s = soft_unicode(s)
|
||||||
|
return s.replace(soft_unicode(old), soft_unicode(new), count)
|
||||||
|
|
||||||
|
|
||||||
|
def do_upper(s):
|
||||||
|
"""Convert a value to uppercase."""
|
||||||
|
return soft_unicode(s).upper()
|
||||||
|
|
||||||
|
|
||||||
|
def do_lower(s):
|
||||||
|
"""Convert a value to lowercase."""
|
||||||
|
return soft_unicode(s).lower()
|
||||||
|
|
||||||
|
|
||||||
|
@evalcontextfilter
|
||||||
|
def do_xmlattr(_eval_ctx, d, autospace=True):
|
||||||
|
"""Create an SGML/XML attribute string based on the items in a dict.
|
||||||
|
All values that are neither `none` nor `undefined` are automatically
|
||||||
|
escaped:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
<ul{{ {'class': 'my_list', 'missing': none,
|
||||||
|
'id': 'list-%d'|format(variable)}|xmlattr }}>
|
||||||
|
...
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Results in something like this:
|
||||||
|
|
||||||
|
.. sourcecode:: html
|
||||||
|
|
||||||
|
<ul class="my_list" id="list-42">
|
||||||
|
...
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
As you can see it automatically prepends a space in front of the item
|
||||||
|
if the filter returned something unless the second parameter is false.
|
||||||
|
"""
|
||||||
|
rv = u' '.join(
|
||||||
|
u'%s="%s"' % (escape(key), escape(value))
|
||||||
|
for key, value in iteritems(d)
|
||||||
|
if value is not None and not isinstance(value, Undefined)
|
||||||
|
)
|
||||||
|
if autospace and rv:
|
||||||
|
rv = u' ' + rv
|
||||||
|
if _eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def do_capitalize(s):
|
||||||
|
"""Capitalize a value. The first character will be uppercase, all others
|
||||||
|
lowercase.
|
||||||
|
"""
|
||||||
|
return soft_unicode(s).capitalize()
|
||||||
|
|
||||||
|
|
||||||
|
def do_title(s):
|
||||||
|
"""Return a titlecased version of the value. I.e. words will start with
|
||||||
|
uppercase letters, all remaining characters are lowercase.
|
||||||
|
"""
|
||||||
|
rv = []
|
||||||
|
for item in re.compile(r'([-\s]+)(?u)').split(s):
|
||||||
|
if not item:
|
||||||
|
continue
|
||||||
|
rv.append(item[0].upper() + item[1:].lower())
|
||||||
|
return ''.join(rv)
|
||||||
|
|
||||||
|
|
||||||
|
def do_dictsort(value, case_sensitive=False, by='key'):
|
||||||
|
"""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
|
||||||
|
key or value:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% for item in mydict|dictsort %}
|
||||||
|
sort the dict by key, case insensitive
|
||||||
|
|
||||||
|
{% for item in mydict|dictsort(true) %}
|
||||||
|
sort the dict by key, case sensitive
|
||||||
|
|
||||||
|
{% for item in mydict|dictsort(false, 'value') %}
|
||||||
|
sort the dict by key, case insensitive, sorted
|
||||||
|
normally and ordered by value.
|
||||||
|
"""
|
||||||
|
if by == 'key':
|
||||||
|
pos = 0
|
||||||
|
elif by == 'value':
|
||||||
|
pos = 1
|
||||||
|
else:
|
||||||
|
raise FilterArgumentError('You can only sort by either '
|
||||||
|
'"key" or "value"')
|
||||||
|
def sort_func(item):
|
||||||
|
value = item[pos]
|
||||||
|
if isinstance(value, string_types) and not case_sensitive:
|
||||||
|
value = value.lower()
|
||||||
|
return value
|
||||||
|
|
||||||
|
return sorted(value.items(), key=sort_func)
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_sort(environment, value, reverse=False, case_sensitive=False,
|
||||||
|
attribute=None):
|
||||||
|
"""Sort an iterable. Per default it sorts ascending, if you pass it
|
||||||
|
true as first argument it will reverse the sorting.
|
||||||
|
|
||||||
|
If the iterable is made of strings the third parameter can be used to
|
||||||
|
control the case sensitiveness of the comparison which is disabled by
|
||||||
|
default.
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% for item in iterable|sort %}
|
||||||
|
...
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
It is also possible to sort by an attribute (for example to sort
|
||||||
|
by the date of an object) by specifying the `attribute` parameter:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% for item in iterable|sort(attribute='date') %}
|
||||||
|
...
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6
|
||||||
|
The `attribute` parameter was added.
|
||||||
|
"""
|
||||||
|
if not case_sensitive:
|
||||||
|
def sort_func(item):
|
||||||
|
if isinstance(item, string_types):
|
||||||
|
item = item.lower()
|
||||||
|
return item
|
||||||
|
else:
|
||||||
|
sort_func = None
|
||||||
|
if attribute is not None:
|
||||||
|
getter = make_attrgetter(environment, attribute)
|
||||||
|
def sort_func(item, processor=sort_func or (lambda x: x)):
|
||||||
|
return processor(getter(item))
|
||||||
|
return sorted(value, key=sort_func, reverse=reverse)
|
||||||
|
|
||||||
|
|
||||||
|
def do_default(value, default_value=u'', boolean=False):
|
||||||
|
"""If the value is undefined it will return the passed default value,
|
||||||
|
otherwise the value of the variable:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ my_variable|default('my_variable is not defined') }}
|
||||||
|
|
||||||
|
This will output the value of ``my_variable`` if the variable was
|
||||||
|
defined, otherwise ``'my_variable is not defined'``. If you want
|
||||||
|
to use default with variables that evaluate to false you have to
|
||||||
|
set the second parameter to `true`:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ ''|default('the string was empty', true) }}
|
||||||
|
"""
|
||||||
|
if isinstance(value, Undefined) or (boolean and not value):
|
||||||
|
return default_value
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@evalcontextfilter
|
||||||
|
def do_join(eval_ctx, value, d=u'', attribute=None):
|
||||||
|
"""Return a string which is the concatenation of the strings in the
|
||||||
|
sequence. The separator between elements is an empty string per
|
||||||
|
default, you can define it with the optional parameter:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ [1, 2, 3]|join('|') }}
|
||||||
|
-> 1|2|3
|
||||||
|
|
||||||
|
{{ [1, 2, 3]|join }}
|
||||||
|
-> 123
|
||||||
|
|
||||||
|
It is also possible to join certain attributes of an object:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ users|join(', ', attribute='username') }}
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
The `attribute` parameter was added.
|
||||||
|
"""
|
||||||
|
if attribute is not None:
|
||||||
|
value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
|
||||||
|
|
||||||
|
# no automatic escaping? joining is a lot eaiser then
|
||||||
|
if not eval_ctx.autoescape:
|
||||||
|
return text_type(d).join(imap(text_type, value))
|
||||||
|
|
||||||
|
# if the delimiter doesn't have an html representation we check
|
||||||
|
# if any of the items has. If yes we do a coercion to Markup
|
||||||
|
if not hasattr(d, '__html__'):
|
||||||
|
value = list(value)
|
||||||
|
do_escape = False
|
||||||
|
for idx, item in enumerate(value):
|
||||||
|
if hasattr(item, '__html__'):
|
||||||
|
do_escape = True
|
||||||
|
else:
|
||||||
|
value[idx] = text_type(item)
|
||||||
|
if do_escape:
|
||||||
|
d = escape(d)
|
||||||
|
else:
|
||||||
|
d = text_type(d)
|
||||||
|
return d.join(value)
|
||||||
|
|
||||||
|
# no html involved, to normal joining
|
||||||
|
return soft_unicode(d).join(imap(soft_unicode, value))
|
||||||
|
|
||||||
|
|
||||||
|
def do_center(value, width=80):
|
||||||
|
"""Centers the value in a field of a given width."""
|
||||||
|
return text_type(value).center(width)
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_first(environment, seq):
|
||||||
|
"""Return the first item of a sequence."""
|
||||||
|
try:
|
||||||
|
return next(iter(seq))
|
||||||
|
except StopIteration:
|
||||||
|
return environment.undefined('No first item, sequence was empty.')
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_last(environment, seq):
|
||||||
|
"""Return the last item of a sequence."""
|
||||||
|
try:
|
||||||
|
return next(iter(reversed(seq)))
|
||||||
|
except StopIteration:
|
||||||
|
return environment.undefined('No last item, sequence was empty.')
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_random(environment, seq):
|
||||||
|
"""Return a random item from the sequence."""
|
||||||
|
try:
|
||||||
|
return choice(seq)
|
||||||
|
except IndexError:
|
||||||
|
return environment.undefined('No random item, sequence was empty.')
|
||||||
|
|
||||||
|
|
||||||
|
def do_filesizeformat(value, binary=False):
|
||||||
|
"""Format the value like a 'human-readable' file size (i.e. 13 kB,
|
||||||
|
4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
|
||||||
|
Giga, etc.), if the second parameter is set to `True` the binary
|
||||||
|
prefixes are used (Mebi, Gibi).
|
||||||
|
"""
|
||||||
|
bytes = float(value)
|
||||||
|
base = binary and 1024 or 1000
|
||||||
|
prefixes = [
|
||||||
|
(binary and 'KiB' or 'kB'),
|
||||||
|
(binary and 'MiB' or 'MB'),
|
||||||
|
(binary and 'GiB' or 'GB'),
|
||||||
|
(binary and 'TiB' or 'TB'),
|
||||||
|
(binary and 'PiB' or 'PB'),
|
||||||
|
(binary and 'EiB' or 'EB'),
|
||||||
|
(binary and 'ZiB' or 'ZB'),
|
||||||
|
(binary and 'YiB' or 'YB')
|
||||||
|
]
|
||||||
|
if bytes == 1:
|
||||||
|
return '1 Byte'
|
||||||
|
elif bytes < base:
|
||||||
|
return '%d Bytes' % bytes
|
||||||
|
else:
|
||||||
|
for i, prefix in enumerate(prefixes):
|
||||||
|
unit = base ** (i + 2)
|
||||||
|
if bytes < unit:
|
||||||
|
return '%.1f %s' % ((base * bytes / unit), prefix)
|
||||||
|
return '%.1f %s' % ((base * bytes / unit), prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def do_pprint(value, verbose=False):
|
||||||
|
"""Pretty print a variable. Useful for debugging.
|
||||||
|
|
||||||
|
With Jinja 1.2 onwards you can pass it a parameter. If this parameter
|
||||||
|
is truthy the output will be more verbose (this requires `pretty`)
|
||||||
|
"""
|
||||||
|
return pformat(value, verbose=verbose)
|
||||||
|
|
||||||
|
|
||||||
|
@evalcontextfilter
|
||||||
|
def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
|
||||||
|
"""Converts URLs in plain text into clickable links.
|
||||||
|
|
||||||
|
If you pass the filter an additional integer it will shorten the urls
|
||||||
|
to that number. Also a third argument exists that makes the urls
|
||||||
|
"nofollow":
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ mytext|urlize(40, true) }}
|
||||||
|
links are shortened to 40 chars and defined with rel="nofollow"
|
||||||
|
"""
|
||||||
|
rv = urlize(value, trim_url_limit, nofollow)
|
||||||
|
if eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def do_indent(s, width=4, indentfirst=False):
|
||||||
|
"""Return a copy of the passed string, each line indented by
|
||||||
|
4 spaces. The first line is not indented. If you want to
|
||||||
|
change the number of spaces or indent the first line too
|
||||||
|
you can pass additional parameters to the filter:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ mytext|indent(2, true) }}
|
||||||
|
indent by two spaces and indent the first line too.
|
||||||
|
"""
|
||||||
|
indention = u' ' * width
|
||||||
|
rv = (u'\n' + indention).join(s.splitlines())
|
||||||
|
if indentfirst:
|
||||||
|
rv = indention + rv
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def do_truncate(s, length=255, killwords=False, end='...'):
|
||||||
|
"""Return a truncated copy of the string. The length is specified
|
||||||
|
with the first parameter which defaults to ``255``. If the second
|
||||||
|
parameter is ``true`` the filter will cut the text at length. Otherwise
|
||||||
|
it will discard the last word. If the text was in fact
|
||||||
|
truncated it will append an ellipsis sign (``"..."``). If you want a
|
||||||
|
different ellipsis sign than ``"..."`` you can specify it using the
|
||||||
|
third parameter.
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ "foo bar"|truncate(5) }}
|
||||||
|
-> "foo ..."
|
||||||
|
{{ "foo bar"|truncate(5, True) }}
|
||||||
|
-> "foo b..."
|
||||||
|
"""
|
||||||
|
if len(s) <= length:
|
||||||
|
return s
|
||||||
|
elif killwords:
|
||||||
|
return s[:length] + end
|
||||||
|
words = s.split(' ')
|
||||||
|
result = []
|
||||||
|
m = 0
|
||||||
|
for word in words:
|
||||||
|
m += len(word) + 1
|
||||||
|
if m > length:
|
||||||
|
break
|
||||||
|
result.append(word)
|
||||||
|
result.append(end)
|
||||||
|
return u' '.join(result)
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_wordwrap(environment, s, width=79, break_long_words=True,
|
||||||
|
wrapstring=None):
|
||||||
|
"""
|
||||||
|
Return a copy of the string passed to the filter wrapped after
|
||||||
|
``79`` characters. You can override this default using the first
|
||||||
|
parameter. If you set the second parameter to `false` Jinja will not
|
||||||
|
split words apart if they are longer than `width`. By default, the newlines
|
||||||
|
will be the default newlines for the environment, but this can be changed
|
||||||
|
using the wrapstring keyword argument.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
Added support for the `wrapstring` parameter.
|
||||||
|
"""
|
||||||
|
if not wrapstring:
|
||||||
|
wrapstring = environment.newline_sequence
|
||||||
|
import textwrap
|
||||||
|
return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False,
|
||||||
|
replace_whitespace=False,
|
||||||
|
break_long_words=break_long_words))
|
||||||
|
|
||||||
|
|
||||||
|
def do_wordcount(s):
|
||||||
|
"""Count the words in that string."""
|
||||||
|
return len(_word_re.findall(s))
|
||||||
|
|
||||||
|
|
||||||
|
def do_int(value, default=0):
|
||||||
|
"""Convert the value into an integer. If the
|
||||||
|
conversion doesn't work it will return ``0``. You can
|
||||||
|
override this default using the first parameter.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# this quirk is necessary so that "42.23"|int gives 42.
|
||||||
|
try:
|
||||||
|
return int(float(value))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def do_float(value, default=0.0):
|
||||||
|
"""Convert the value into a floating point number. If the
|
||||||
|
conversion doesn't work it will return ``0.0``. You can
|
||||||
|
override this default using the first parameter.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def do_format(value, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Apply python string formatting on an object:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ "%s - %s"|format("Hello?", "Foo!") }}
|
||||||
|
-> Hello? - Foo!
|
||||||
|
"""
|
||||||
|
if args and kwargs:
|
||||||
|
raise FilterArgumentError('can\'t handle positional and keyword '
|
||||||
|
'arguments at the same time')
|
||||||
|
return soft_unicode(value) % (kwargs or args)
|
||||||
|
|
||||||
|
|
||||||
|
def do_trim(value):
|
||||||
|
"""Strip leading and trailing whitespace."""
|
||||||
|
return soft_unicode(value).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def do_striptags(value):
|
||||||
|
"""Strip SGML/XML tags and replace adjacent whitespace by one space.
|
||||||
|
"""
|
||||||
|
if hasattr(value, '__html__'):
|
||||||
|
value = value.__html__()
|
||||||
|
return Markup(text_type(value)).striptags()
|
||||||
|
|
||||||
|
|
||||||
|
def do_slice(value, slices, fill_with=None):
|
||||||
|
"""Slice an iterator and return a list of lists containing
|
||||||
|
those items. Useful if you want to create a div containing
|
||||||
|
three ul tags that represent columns:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
<div class="columwrapper">
|
||||||
|
{%- for column in items|slice(3) %}
|
||||||
|
<ul class="column-{{ loop.index }}">
|
||||||
|
{%- for item in column %}
|
||||||
|
<li>{{ item }}</li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{%- endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
If you pass it a second argument it's used to fill missing
|
||||||
|
values on the last iteration.
|
||||||
|
"""
|
||||||
|
seq = list(value)
|
||||||
|
length = len(seq)
|
||||||
|
items_per_slice = length // slices
|
||||||
|
slices_with_extra = length % slices
|
||||||
|
offset = 0
|
||||||
|
for slice_number in range(slices):
|
||||||
|
start = offset + slice_number * items_per_slice
|
||||||
|
if slice_number < slices_with_extra:
|
||||||
|
offset += 1
|
||||||
|
end = offset + (slice_number + 1) * items_per_slice
|
||||||
|
tmp = seq[start:end]
|
||||||
|
if fill_with is not None and slice_number >= slices_with_extra:
|
||||||
|
tmp.append(fill_with)
|
||||||
|
yield tmp
|
||||||
|
|
||||||
|
|
||||||
|
def do_batch(value, linecount, fill_with=None):
|
||||||
|
"""
|
||||||
|
A filter that batches items. It works pretty much like `slice`
|
||||||
|
just the other way round. It returns a list of lists with the
|
||||||
|
given number of items. If you provide a second parameter this
|
||||||
|
is used to fill up missing items. See this example:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{%- for row in items|batch(3, ' ') %}
|
||||||
|
<tr>
|
||||||
|
{%- for column in row %}
|
||||||
|
<td>{{ column }}</td>
|
||||||
|
{%- endfor %}
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
tmp = []
|
||||||
|
for item in value:
|
||||||
|
if len(tmp) == linecount:
|
||||||
|
yield tmp
|
||||||
|
tmp = []
|
||||||
|
tmp.append(item)
|
||||||
|
if tmp:
|
||||||
|
if fill_with is not None and len(tmp) < linecount:
|
||||||
|
tmp += [fill_with] * (linecount - len(tmp))
|
||||||
|
yield tmp
|
||||||
|
|
||||||
|
|
||||||
|
def do_round(value, precision=0, method='common'):
|
||||||
|
"""Round the number to a given precision. The first
|
||||||
|
parameter specifies the precision (default is ``0``), the
|
||||||
|
second the rounding method:
|
||||||
|
|
||||||
|
- ``'common'`` rounds either up or down
|
||||||
|
- ``'ceil'`` always rounds up
|
||||||
|
- ``'floor'`` always rounds down
|
||||||
|
|
||||||
|
If you don't specify a method ``'common'`` is used.
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ 42.55|round }}
|
||||||
|
-> 43.0
|
||||||
|
{{ 42.55|round(1, 'floor') }}
|
||||||
|
-> 42.5
|
||||||
|
|
||||||
|
Note that even if rounded to 0 precision, a float is returned. If
|
||||||
|
you need a real integer, pipe it through `int`:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ 42.55|round|int }}
|
||||||
|
-> 43
|
||||||
|
"""
|
||||||
|
if not method in ('common', 'ceil', 'floor'):
|
||||||
|
raise FilterArgumentError('method must be common, ceil or floor')
|
||||||
|
if method == 'common':
|
||||||
|
return round(value, precision)
|
||||||
|
func = getattr(math, method)
|
||||||
|
return func(value * (10 ** precision)) / (10 ** precision)
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_groupby(environment, value, attribute):
|
||||||
|
"""Group a sequence of objects by a common attribute.
|
||||||
|
|
||||||
|
If you for example have a list of dicts or objects that represent persons
|
||||||
|
with `gender`, `first_name` and `last_name` attributes and you want to
|
||||||
|
group all users by genders you can do something like the following
|
||||||
|
snippet:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for group in persons|groupby('gender') %}
|
||||||
|
<li>{{ group.grouper }}<ul>
|
||||||
|
{% for person in group.list %}
|
||||||
|
<li>{{ person.first_name }} {{ person.last_name }}</li>
|
||||||
|
{% endfor %}</ul></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Additionally it's possible to use tuple unpacking for the grouper and
|
||||||
|
list:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for grouper, list in persons|groupby('gender') %}
|
||||||
|
...
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
As you can see the item we're grouping by is stored in the `grouper`
|
||||||
|
attribute and the `list` contains all the objects that have this grouper
|
||||||
|
in common.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6
|
||||||
|
It's now possible to use dotted notation to group by the child
|
||||||
|
attribute of another attribute.
|
||||||
|
"""
|
||||||
|
expr = make_attrgetter(environment, attribute)
|
||||||
|
return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
|
||||||
|
|
||||||
|
|
||||||
|
class _GroupTuple(tuple):
|
||||||
|
__slots__ = ()
|
||||||
|
grouper = property(itemgetter(0))
|
||||||
|
list = property(itemgetter(1))
|
||||||
|
|
||||||
|
def __new__(cls, xxx_todo_changeme):
|
||||||
|
(key, value) = xxx_todo_changeme
|
||||||
|
return tuple.__new__(cls, (key, list(value)))
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_sum(environment, iterable, attribute=None, start=0):
|
||||||
|
"""Returns the sum of a sequence of numbers plus the value of parameter
|
||||||
|
'start' (which defaults to 0). When the sequence is empty it returns
|
||||||
|
start.
|
||||||
|
|
||||||
|
It is also possible to sum up only certain attributes:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
Total: {{ items|sum(attribute='price') }}
|
||||||
|
|
||||||
|
.. versionchanged:: 2.6
|
||||||
|
The `attribute` parameter was added to allow suming up over
|
||||||
|
attributes. Also the `start` parameter was moved on to the right.
|
||||||
|
"""
|
||||||
|
if attribute is not None:
|
||||||
|
iterable = imap(make_attrgetter(environment, attribute), iterable)
|
||||||
|
return sum(iterable, start)
|
||||||
|
|
||||||
|
|
||||||
|
def do_list(value):
|
||||||
|
"""Convert the value into a list. If it was a string the returned list
|
||||||
|
will be a list of characters.
|
||||||
|
"""
|
||||||
|
return list(value)
|
||||||
|
|
||||||
|
|
||||||
|
def do_mark_safe(value):
|
||||||
|
"""Mark the value as safe which means that in an environment with automatic
|
||||||
|
escaping enabled this variable will not be escaped.
|
||||||
|
"""
|
||||||
|
return Markup(value)
|
||||||
|
|
||||||
|
|
||||||
|
def do_mark_unsafe(value):
|
||||||
|
"""Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
|
||||||
|
return text_type(value)
|
||||||
|
|
||||||
|
|
||||||
|
def do_reverse(value):
|
||||||
|
"""Reverse the object or return an iterator the iterates over it the other
|
||||||
|
way round.
|
||||||
|
"""
|
||||||
|
if isinstance(value, string_types):
|
||||||
|
return value[::-1]
|
||||||
|
try:
|
||||||
|
return reversed(value)
|
||||||
|
except TypeError:
|
||||||
|
try:
|
||||||
|
rv = list(value)
|
||||||
|
rv.reverse()
|
||||||
|
return rv
|
||||||
|
except TypeError:
|
||||||
|
raise FilterArgumentError('argument must be iterable')
|
||||||
|
|
||||||
|
|
||||||
|
@environmentfilter
|
||||||
|
def do_attr(environment, obj, name):
|
||||||
|
"""Get an attribute of an object. ``foo|attr("bar")`` works like
|
||||||
|
``foo["bar"]`` just that always an attribute is returned and items are not
|
||||||
|
looked up.
|
||||||
|
|
||||||
|
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
name = str(name)
|
||||||
|
except UnicodeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = getattr(obj, name)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if environment.sandboxed and not \
|
||||||
|
environment.is_safe_attribute(obj, name, value):
|
||||||
|
return environment.unsafe_undefined(obj, name)
|
||||||
|
return value
|
||||||
|
return environment.undefined(obj=obj, name=name)
|
||||||
|
|
||||||
|
|
||||||
|
@contextfilter
|
||||||
|
def do_map(*args, **kwargs):
|
||||||
|
"""Applies a filter on a sequence of objects or looks up an attribute.
|
||||||
|
This is useful when dealing with lists of objects but you are really
|
||||||
|
only interested in a certain value of it.
|
||||||
|
|
||||||
|
The basic usage is mapping on an attribute. Imagine you have a list
|
||||||
|
of users but you are only interested in a list of usernames:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
Users on this page: {{ users|map(attribute='username')|join(', ') }}
|
||||||
|
|
||||||
|
Alternatively you can let it invoke a filter by passing the name of the
|
||||||
|
filter and the arguments afterwards. A good example would be applying a
|
||||||
|
text conversion filter on a sequence:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
Users on this page: {{ titles|map('lower')|join(', ') }}
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
"""
|
||||||
|
context = args[0]
|
||||||
|
seq = args[1]
|
||||||
|
|
||||||
|
if len(args) == 2 and 'attribute' in kwargs:
|
||||||
|
attribute = kwargs.pop('attribute')
|
||||||
|
if kwargs:
|
||||||
|
raise FilterArgumentError('Unexpected keyword argument %r' %
|
||||||
|
next(iter(kwargs)))
|
||||||
|
func = make_attrgetter(context.environment, attribute)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
name = args[2]
|
||||||
|
args = args[3:]
|
||||||
|
except LookupError:
|
||||||
|
raise FilterArgumentError('map requires a filter argument')
|
||||||
|
func = lambda item: context.environment.call_filter(
|
||||||
|
name, item, args, kwargs, context=context)
|
||||||
|
|
||||||
|
if seq:
|
||||||
|
for item in seq:
|
||||||
|
yield func(item)
|
||||||
|
|
||||||
|
|
||||||
|
@contextfilter
|
||||||
|
def do_select(*args, **kwargs):
|
||||||
|
"""Filters a sequence of objects by appying a test to either the object
|
||||||
|
or the attribute and only selecting the ones with the test succeeding.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ numbers|select("odd") }}
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
"""
|
||||||
|
return _select_or_reject(args, kwargs, lambda x: x, False)
|
||||||
|
|
||||||
|
|
||||||
|
@contextfilter
|
||||||
|
def do_reject(*args, **kwargs):
|
||||||
|
"""Filters a sequence of objects by appying a test to either the object
|
||||||
|
or the attribute and rejecting the ones with the test succeeding.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ numbers|reject("odd") }}
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
"""
|
||||||
|
return _select_or_reject(args, kwargs, lambda x: not x, False)
|
||||||
|
|
||||||
|
|
||||||
|
@contextfilter
|
||||||
|
def do_selectattr(*args, **kwargs):
|
||||||
|
"""Filters a sequence of objects by appying a test to either the object
|
||||||
|
or the attribute and only selecting the ones with the test succeeding.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ users|selectattr("is_active") }}
|
||||||
|
{{ users|selectattr("email", "none") }}
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
"""
|
||||||
|
return _select_or_reject(args, kwargs, lambda x: x, True)
|
||||||
|
|
||||||
|
|
||||||
|
@contextfilter
|
||||||
|
def do_rejectattr(*args, **kwargs):
|
||||||
|
"""Filters a sequence of objects by appying a test to either the object
|
||||||
|
or the attribute and rejecting the ones with the test succeeding.
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{{ users|rejectattr("is_active") }}
|
||||||
|
{{ users|rejectattr("email", "none") }}
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
"""
|
||||||
|
return _select_or_reject(args, kwargs, lambda x: not x, True)
|
||||||
|
|
||||||
|
|
||||||
|
def _select_or_reject(args, kwargs, modfunc, lookup_attr):
|
||||||
|
context = args[0]
|
||||||
|
seq = args[1]
|
||||||
|
if lookup_attr:
|
||||||
|
try:
|
||||||
|
attr = args[2]
|
||||||
|
except LookupError:
|
||||||
|
raise FilterArgumentError('Missing parameter for attribute name')
|
||||||
|
transfunc = make_attrgetter(context.environment, attr)
|
||||||
|
off = 1
|
||||||
|
else:
|
||||||
|
off = 0
|
||||||
|
transfunc = lambda x: x
|
||||||
|
|
||||||
|
try:
|
||||||
|
name = args[2 + off]
|
||||||
|
args = args[3 + off:]
|
||||||
|
func = lambda item: context.environment.call_test(
|
||||||
|
name, item, args, kwargs)
|
||||||
|
except LookupError:
|
||||||
|
func = bool
|
||||||
|
|
||||||
|
if seq:
|
||||||
|
for item in seq:
|
||||||
|
if modfunc(func(transfunc(item))):
|
||||||
|
yield item
|
||||||
|
|
||||||
|
|
||||||
|
FILTERS = {
|
||||||
|
'attr': do_attr,
|
||||||
|
'replace': do_replace,
|
||||||
|
'upper': do_upper,
|
||||||
|
'lower': do_lower,
|
||||||
|
'escape': escape,
|
||||||
|
'e': escape,
|
||||||
|
'forceescape': do_forceescape,
|
||||||
|
'capitalize': do_capitalize,
|
||||||
|
'title': do_title,
|
||||||
|
'default': do_default,
|
||||||
|
'd': do_default,
|
||||||
|
'join': do_join,
|
||||||
|
'count': len,
|
||||||
|
'dictsort': do_dictsort,
|
||||||
|
'sort': do_sort,
|
||||||
|
'length': len,
|
||||||
|
'reverse': do_reverse,
|
||||||
|
'center': do_center,
|
||||||
|
'indent': do_indent,
|
||||||
|
'title': do_title,
|
||||||
|
'capitalize': do_capitalize,
|
||||||
|
'first': do_first,
|
||||||
|
'last': do_last,
|
||||||
|
'map': do_map,
|
||||||
|
'random': do_random,
|
||||||
|
'reject': do_reject,
|
||||||
|
'rejectattr': do_rejectattr,
|
||||||
|
'filesizeformat': do_filesizeformat,
|
||||||
|
'pprint': do_pprint,
|
||||||
|
'truncate': do_truncate,
|
||||||
|
'wordwrap': do_wordwrap,
|
||||||
|
'wordcount': do_wordcount,
|
||||||
|
'int': do_int,
|
||||||
|
'float': do_float,
|
||||||
|
'string': soft_unicode,
|
||||||
|
'list': do_list,
|
||||||
|
'urlize': do_urlize,
|
||||||
|
'format': do_format,
|
||||||
|
'trim': do_trim,
|
||||||
|
'striptags': do_striptags,
|
||||||
|
'select': do_select,
|
||||||
|
'selectattr': do_selectattr,
|
||||||
|
'slice': do_slice,
|
||||||
|
'batch': do_batch,
|
||||||
|
'sum': do_sum,
|
||||||
|
'abs': abs,
|
||||||
|
'round': do_round,
|
||||||
|
'groupby': do_groupby,
|
||||||
|
'safe': do_mark_safe,
|
||||||
|
'xmlattr': do_xmlattr,
|
||||||
|
'urlencode': do_urlencode
|
||||||
|
}
|
733
tools/external/jinja2/lexer.py
vendored
Normal file
733
tools/external/jinja2/lexer.py
vendored
Normal file
|
@ -0,0 +1,733 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.lexer
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module implements a Jinja / Python combination lexer. The
|
||||||
|
`Lexer` class provided by this module is used to do some preprocessing
|
||||||
|
for Jinja.
|
||||||
|
|
||||||
|
On the one hand it filters out invalid operators like the bitshift
|
||||||
|
operators we don't allow in templates. On the other hand it separates
|
||||||
|
template code and python code in expressions.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
from operator import itemgetter
|
||||||
|
from collections import deque
|
||||||
|
from jinja2.exceptions import TemplateSyntaxError
|
||||||
|
from jinja2.utils import LRUCache
|
||||||
|
from jinja2._compat import next, iteritems, implements_iterator, text_type, \
|
||||||
|
intern
|
||||||
|
|
||||||
|
|
||||||
|
# cache for the lexers. Exists in order to be able to have multiple
|
||||||
|
# environments with the same lexer
|
||||||
|
_lexer_cache = LRUCache(50)
|
||||||
|
|
||||||
|
# static regular expressions
|
||||||
|
whitespace_re = re.compile(r'\s+', re.U)
|
||||||
|
string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
||||||
|
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
||||||
|
integer_re = re.compile(r'\d+')
|
||||||
|
|
||||||
|
# we use the unicode identifier rule if this python version is able
|
||||||
|
# to handle unicode identifiers, otherwise the standard ASCII one.
|
||||||
|
try:
|
||||||
|
compile('föö', '<unknown>', 'eval')
|
||||||
|
except SyntaxError:
|
||||||
|
name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
|
||||||
|
else:
|
||||||
|
from jinja2 import _stringdefs
|
||||||
|
name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
|
||||||
|
_stringdefs.xid_continue))
|
||||||
|
|
||||||
|
float_re = re.compile(r'(?<!\.)\d+\.\d+')
|
||||||
|
newline_re = re.compile(r'(\r\n|\r|\n)')
|
||||||
|
|
||||||
|
# internal the tokens and keep references to them
|
||||||
|
TOKEN_ADD = intern('add')
|
||||||
|
TOKEN_ASSIGN = intern('assign')
|
||||||
|
TOKEN_COLON = intern('colon')
|
||||||
|
TOKEN_COMMA = intern('comma')
|
||||||
|
TOKEN_DIV = intern('div')
|
||||||
|
TOKEN_DOT = intern('dot')
|
||||||
|
TOKEN_EQ = intern('eq')
|
||||||
|
TOKEN_FLOORDIV = intern('floordiv')
|
||||||
|
TOKEN_GT = intern('gt')
|
||||||
|
TOKEN_GTEQ = intern('gteq')
|
||||||
|
TOKEN_LBRACE = intern('lbrace')
|
||||||
|
TOKEN_LBRACKET = intern('lbracket')
|
||||||
|
TOKEN_LPAREN = intern('lparen')
|
||||||
|
TOKEN_LT = intern('lt')
|
||||||
|
TOKEN_LTEQ = intern('lteq')
|
||||||
|
TOKEN_MOD = intern('mod')
|
||||||
|
TOKEN_MUL = intern('mul')
|
||||||
|
TOKEN_NE = intern('ne')
|
||||||
|
TOKEN_PIPE = intern('pipe')
|
||||||
|
TOKEN_POW = intern('pow')
|
||||||
|
TOKEN_RBRACE = intern('rbrace')
|
||||||
|
TOKEN_RBRACKET = intern('rbracket')
|
||||||
|
TOKEN_RPAREN = intern('rparen')
|
||||||
|
TOKEN_SEMICOLON = intern('semicolon')
|
||||||
|
TOKEN_SUB = intern('sub')
|
||||||
|
TOKEN_TILDE = intern('tilde')
|
||||||
|
TOKEN_WHITESPACE = intern('whitespace')
|
||||||
|
TOKEN_FLOAT = intern('float')
|
||||||
|
TOKEN_INTEGER = intern('integer')
|
||||||
|
TOKEN_NAME = intern('name')
|
||||||
|
TOKEN_STRING = intern('string')
|
||||||
|
TOKEN_OPERATOR = intern('operator')
|
||||||
|
TOKEN_BLOCK_BEGIN = intern('block_begin')
|
||||||
|
TOKEN_BLOCK_END = intern('block_end')
|
||||||
|
TOKEN_VARIABLE_BEGIN = intern('variable_begin')
|
||||||
|
TOKEN_VARIABLE_END = intern('variable_end')
|
||||||
|
TOKEN_RAW_BEGIN = intern('raw_begin')
|
||||||
|
TOKEN_RAW_END = intern('raw_end')
|
||||||
|
TOKEN_COMMENT_BEGIN = intern('comment_begin')
|
||||||
|
TOKEN_COMMENT_END = intern('comment_end')
|
||||||
|
TOKEN_COMMENT = intern('comment')
|
||||||
|
TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin')
|
||||||
|
TOKEN_LINESTATEMENT_END = intern('linestatement_end')
|
||||||
|
TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin')
|
||||||
|
TOKEN_LINECOMMENT_END = intern('linecomment_end')
|
||||||
|
TOKEN_LINECOMMENT = intern('linecomment')
|
||||||
|
TOKEN_DATA = intern('data')
|
||||||
|
TOKEN_INITIAL = intern('initial')
|
||||||
|
TOKEN_EOF = intern('eof')
|
||||||
|
|
||||||
|
# bind operators to token types
|
||||||
|
operators = {
|
||||||
|
'+': TOKEN_ADD,
|
||||||
|
'-': TOKEN_SUB,
|
||||||
|
'/': TOKEN_DIV,
|
||||||
|
'//': TOKEN_FLOORDIV,
|
||||||
|
'*': TOKEN_MUL,
|
||||||
|
'%': TOKEN_MOD,
|
||||||
|
'**': TOKEN_POW,
|
||||||
|
'~': TOKEN_TILDE,
|
||||||
|
'[': TOKEN_LBRACKET,
|
||||||
|
']': TOKEN_RBRACKET,
|
||||||
|
'(': TOKEN_LPAREN,
|
||||||
|
')': TOKEN_RPAREN,
|
||||||
|
'{': TOKEN_LBRACE,
|
||||||
|
'}': TOKEN_RBRACE,
|
||||||
|
'==': TOKEN_EQ,
|
||||||
|
'!=': TOKEN_NE,
|
||||||
|
'>': TOKEN_GT,
|
||||||
|
'>=': TOKEN_GTEQ,
|
||||||
|
'<': TOKEN_LT,
|
||||||
|
'<=': TOKEN_LTEQ,
|
||||||
|
'=': TOKEN_ASSIGN,
|
||||||
|
'.': TOKEN_DOT,
|
||||||
|
':': TOKEN_COLON,
|
||||||
|
'|': TOKEN_PIPE,
|
||||||
|
',': TOKEN_COMMA,
|
||||||
|
';': TOKEN_SEMICOLON
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_operators = dict([(v, k) for k, v in iteritems(operators)])
|
||||||
|
assert len(operators) == len(reverse_operators), 'operators dropped'
|
||||||
|
operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
|
||||||
|
sorted(operators, key=lambda x: -len(x))))
|
||||||
|
|
||||||
|
ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
|
||||||
|
TOKEN_COMMENT_END, TOKEN_WHITESPACE,
|
||||||
|
TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
|
||||||
|
TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
|
||||||
|
ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
|
||||||
|
TOKEN_COMMENT, TOKEN_LINECOMMENT])
|
||||||
|
|
||||||
|
|
||||||
|
def _describe_token_type(token_type):
|
||||||
|
if token_type in reverse_operators:
|
||||||
|
return reverse_operators[token_type]
|
||||||
|
return {
|
||||||
|
TOKEN_COMMENT_BEGIN: 'begin of comment',
|
||||||
|
TOKEN_COMMENT_END: 'end of comment',
|
||||||
|
TOKEN_COMMENT: 'comment',
|
||||||
|
TOKEN_LINECOMMENT: 'comment',
|
||||||
|
TOKEN_BLOCK_BEGIN: 'begin of statement block',
|
||||||
|
TOKEN_BLOCK_END: 'end of statement block',
|
||||||
|
TOKEN_VARIABLE_BEGIN: 'begin of print statement',
|
||||||
|
TOKEN_VARIABLE_END: 'end of print statement',
|
||||||
|
TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement',
|
||||||
|
TOKEN_LINESTATEMENT_END: 'end of line statement',
|
||||||
|
TOKEN_DATA: 'template data / text',
|
||||||
|
TOKEN_EOF: 'end of template'
|
||||||
|
}.get(token_type, token_type)
|
||||||
|
|
||||||
|
|
||||||
|
def describe_token(token):
|
||||||
|
"""Returns a description of the token."""
|
||||||
|
if token.type == 'name':
|
||||||
|
return token.value
|
||||||
|
return _describe_token_type(token.type)
|
||||||
|
|
||||||
|
|
||||||
|
def describe_token_expr(expr):
|
||||||
|
"""Like `describe_token` but for token expressions."""
|
||||||
|
if ':' in expr:
|
||||||
|
type, value = expr.split(':', 1)
|
||||||
|
if type == 'name':
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
type = expr
|
||||||
|
return _describe_token_type(type)
|
||||||
|
|
||||||
|
|
||||||
|
def count_newlines(value):
|
||||||
|
"""Count the number of newline characters in the string. This is
|
||||||
|
useful for extensions that filter a stream.
|
||||||
|
"""
|
||||||
|
return len(newline_re.findall(value))
|
||||||
|
|
||||||
|
|
||||||
|
def compile_rules(environment):
|
||||||
|
"""Compiles all the rules from the environment into a list of rules."""
|
||||||
|
e = re.escape
|
||||||
|
rules = [
|
||||||
|
(len(environment.comment_start_string), 'comment',
|
||||||
|
e(environment.comment_start_string)),
|
||||||
|
(len(environment.block_start_string), 'block',
|
||||||
|
e(environment.block_start_string)),
|
||||||
|
(len(environment.variable_start_string), 'variable',
|
||||||
|
e(environment.variable_start_string))
|
||||||
|
]
|
||||||
|
|
||||||
|
if environment.line_statement_prefix is not None:
|
||||||
|
rules.append((len(environment.line_statement_prefix), 'linestatement',
|
||||||
|
r'^[ \t\v]*' + e(environment.line_statement_prefix)))
|
||||||
|
if environment.line_comment_prefix is not None:
|
||||||
|
rules.append((len(environment.line_comment_prefix), 'linecomment',
|
||||||
|
r'(?:^|(?<=\S))[^\S\r\n]*' +
|
||||||
|
e(environment.line_comment_prefix)))
|
||||||
|
|
||||||
|
return [x[1:] for x in sorted(rules, reverse=True)]
|
||||||
|
|
||||||
|
|
||||||
|
class Failure(object):
|
||||||
|
"""Class that raises a `TemplateSyntaxError` if called.
|
||||||
|
Used by the `Lexer` to specify known errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message, cls=TemplateSyntaxError):
|
||||||
|
self.message = message
|
||||||
|
self.error_class = cls
|
||||||
|
|
||||||
|
def __call__(self, lineno, filename):
|
||||||
|
raise self.error_class(self.message, lineno, filename)
|
||||||
|
|
||||||
|
|
||||||
|
class Token(tuple):
|
||||||
|
"""Token class."""
|
||||||
|
__slots__ = ()
|
||||||
|
lineno, type, value = (property(itemgetter(x)) for x in range(3))
|
||||||
|
|
||||||
|
def __new__(cls, lineno, type, value):
|
||||||
|
return tuple.__new__(cls, (lineno, intern(str(type)), value))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.type in reverse_operators:
|
||||||
|
return reverse_operators[self.type]
|
||||||
|
elif self.type == 'name':
|
||||||
|
return self.value
|
||||||
|
return self.type
|
||||||
|
|
||||||
|
def test(self, expr):
|
||||||
|
"""Test a token against a token expression. This can either be a
|
||||||
|
token type or ``'token_type:token_value'``. This can only test
|
||||||
|
against string values and types.
|
||||||
|
"""
|
||||||
|
# here we do a regular string equality check as test_any is usually
|
||||||
|
# passed an iterable of not interned strings.
|
||||||
|
if self.type == expr:
|
||||||
|
return True
|
||||||
|
elif ':' in expr:
|
||||||
|
return expr.split(':', 1) == [self.type, self.value]
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_any(self, *iterable):
|
||||||
|
"""Test against multiple token expressions."""
|
||||||
|
for expr in iterable:
|
||||||
|
if self.test(expr):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Token(%r, %r, %r)' % (
|
||||||
|
self.lineno,
|
||||||
|
self.type,
|
||||||
|
self.value
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@implements_iterator
|
||||||
|
class TokenStreamIterator(object):
|
||||||
|
"""The iterator for tokenstreams. Iterate over the stream
|
||||||
|
until the eof token is reached.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
token = self.stream.current
|
||||||
|
if token.type is TOKEN_EOF:
|
||||||
|
self.stream.close()
|
||||||
|
raise StopIteration()
|
||||||
|
next(self.stream)
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
@implements_iterator
|
||||||
|
class TokenStream(object):
|
||||||
|
"""A token stream is an iterable that yields :class:`Token`\s. The
|
||||||
|
parser however does not iterate over it but calls :meth:`next` to go
|
||||||
|
one token ahead. The current active token is stored as :attr:`current`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, generator, name, filename):
|
||||||
|
self._iter = iter(generator)
|
||||||
|
self._pushed = deque()
|
||||||
|
self.name = name
|
||||||
|
self.filename = filename
|
||||||
|
self.closed = False
|
||||||
|
self.current = Token(1, TOKEN_INITIAL, '')
|
||||||
|
next(self)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return TokenStreamIterator(self)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return bool(self._pushed) or self.current.type is not TOKEN_EOF
|
||||||
|
__nonzero__ = __bool__ # py2
|
||||||
|
|
||||||
|
eos = property(lambda x: not x, doc="Are we at the end of the stream?")
|
||||||
|
|
||||||
|
def push(self, token):
|
||||||
|
"""Push a token back to the stream."""
|
||||||
|
self._pushed.append(token)
|
||||||
|
|
||||||
|
def look(self):
|
||||||
|
"""Look at the next token."""
|
||||||
|
old_token = next(self)
|
||||||
|
result = self.current
|
||||||
|
self.push(result)
|
||||||
|
self.current = old_token
|
||||||
|
return result
|
||||||
|
|
||||||
|
def skip(self, n=1):
|
||||||
|
"""Got n tokens ahead."""
|
||||||
|
for x in range(n):
|
||||||
|
next(self)
|
||||||
|
|
||||||
|
def next_if(self, expr):
|
||||||
|
"""Perform the token test and return the token if it matched.
|
||||||
|
Otherwise the return value is `None`.
|
||||||
|
"""
|
||||||
|
if self.current.test(expr):
|
||||||
|
return next(self)
|
||||||
|
|
||||||
|
def skip_if(self, expr):
|
||||||
|
"""Like :meth:`next_if` but only returns `True` or `False`."""
|
||||||
|
return self.next_if(expr) is not None
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
"""Go one token ahead and return the old one"""
|
||||||
|
rv = self.current
|
||||||
|
if self._pushed:
|
||||||
|
self.current = self._pushed.popleft()
|
||||||
|
elif self.current.type is not TOKEN_EOF:
|
||||||
|
try:
|
||||||
|
self.current = next(self._iter)
|
||||||
|
except StopIteration:
|
||||||
|
self.close()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close the stream."""
|
||||||
|
self.current = Token(self.current.lineno, TOKEN_EOF, '')
|
||||||
|
self._iter = None
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
def expect(self, expr):
|
||||||
|
"""Expect a given token type and return it. This accepts the same
|
||||||
|
argument as :meth:`jinja2.lexer.Token.test`.
|
||||||
|
"""
|
||||||
|
if not self.current.test(expr):
|
||||||
|
expr = describe_token_expr(expr)
|
||||||
|
if self.current.type is TOKEN_EOF:
|
||||||
|
raise TemplateSyntaxError('unexpected end of template, '
|
||||||
|
'expected %r.' % expr,
|
||||||
|
self.current.lineno,
|
||||||
|
self.name, self.filename)
|
||||||
|
raise TemplateSyntaxError("expected token %r, got %r" %
|
||||||
|
(expr, describe_token(self.current)),
|
||||||
|
self.current.lineno,
|
||||||
|
self.name, self.filename)
|
||||||
|
try:
|
||||||
|
return self.current
|
||||||
|
finally:
|
||||||
|
next(self)
|
||||||
|
|
||||||
|
|
||||||
|
def get_lexer(environment):
|
||||||
|
"""Return a lexer which is probably cached."""
|
||||||
|
key = (environment.block_start_string,
|
||||||
|
environment.block_end_string,
|
||||||
|
environment.variable_start_string,
|
||||||
|
environment.variable_end_string,
|
||||||
|
environment.comment_start_string,
|
||||||
|
environment.comment_end_string,
|
||||||
|
environment.line_statement_prefix,
|
||||||
|
environment.line_comment_prefix,
|
||||||
|
environment.trim_blocks,
|
||||||
|
environment.lstrip_blocks,
|
||||||
|
environment.newline_sequence,
|
||||||
|
environment.keep_trailing_newline)
|
||||||
|
lexer = _lexer_cache.get(key)
|
||||||
|
if lexer is None:
|
||||||
|
lexer = Lexer(environment)
|
||||||
|
_lexer_cache[key] = lexer
|
||||||
|
return lexer
|
||||||
|
|
||||||
|
|
||||||
|
class Lexer(object):
|
||||||
|
"""Class that implements a lexer for a given environment. Automatically
|
||||||
|
created by the environment class, usually you don't have to do that.
|
||||||
|
|
||||||
|
Note that the lexer is not automatically bound to an environment.
|
||||||
|
Multiple environments can share the same lexer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, environment):
|
||||||
|
# shortcuts
|
||||||
|
c = lambda x: re.compile(x, re.M | re.S)
|
||||||
|
e = re.escape
|
||||||
|
|
||||||
|
# lexing rules for tags
|
||||||
|
tag_rules = [
|
||||||
|
(whitespace_re, TOKEN_WHITESPACE, None),
|
||||||
|
(float_re, TOKEN_FLOAT, None),
|
||||||
|
(integer_re, TOKEN_INTEGER, None),
|
||||||
|
(name_re, TOKEN_NAME, None),
|
||||||
|
(string_re, TOKEN_STRING, None),
|
||||||
|
(operator_re, TOKEN_OPERATOR, None)
|
||||||
|
]
|
||||||
|
|
||||||
|
# assemble the root lexing rule. because "|" is ungreedy
|
||||||
|
# we have to sort by length so that the lexer continues working
|
||||||
|
# as expected when we have parsing rules like <% for block and
|
||||||
|
# <%= for variables. (if someone wants asp like syntax)
|
||||||
|
# variables are just part of the rules if variable processing
|
||||||
|
# is required.
|
||||||
|
root_tag_rules = compile_rules(environment)
|
||||||
|
|
||||||
|
# block suffix if trimming is enabled
|
||||||
|
block_suffix_re = environment.trim_blocks and '\\n?' or ''
|
||||||
|
|
||||||
|
# strip leading spaces if lstrip_blocks is enabled
|
||||||
|
prefix_re = {}
|
||||||
|
if environment.lstrip_blocks:
|
||||||
|
# use '{%+' to manually disable lstrip_blocks behavior
|
||||||
|
no_lstrip_re = e('+')
|
||||||
|
# detect overlap between block and variable or comment strings
|
||||||
|
block_diff = c(r'^%s(.*)' % e(environment.block_start_string))
|
||||||
|
# make sure we don't mistake a block for a variable or a comment
|
||||||
|
m = block_diff.match(environment.comment_start_string)
|
||||||
|
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||||
|
m = block_diff.match(environment.variable_start_string)
|
||||||
|
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||||
|
|
||||||
|
# detect overlap between comment and variable strings
|
||||||
|
comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string))
|
||||||
|
m = comment_diff.match(environment.variable_start_string)
|
||||||
|
no_variable_re = m and r'(?!%s)' % e(m.group(1)) or ''
|
||||||
|
|
||||||
|
lstrip_re = r'^[ \t]*'
|
||||||
|
block_prefix_re = r'%s%s(?!%s)|%s\+?' % (
|
||||||
|
lstrip_re,
|
||||||
|
e(environment.block_start_string),
|
||||||
|
no_lstrip_re,
|
||||||
|
e(environment.block_start_string),
|
||||||
|
)
|
||||||
|
comment_prefix_re = r'%s%s%s|%s\+?' % (
|
||||||
|
lstrip_re,
|
||||||
|
e(environment.comment_start_string),
|
||||||
|
no_variable_re,
|
||||||
|
e(environment.comment_start_string),
|
||||||
|
)
|
||||||
|
prefix_re['block'] = block_prefix_re
|
||||||
|
prefix_re['comment'] = comment_prefix_re
|
||||||
|
else:
|
||||||
|
block_prefix_re = '%s' % e(environment.block_start_string)
|
||||||
|
|
||||||
|
self.newline_sequence = environment.newline_sequence
|
||||||
|
self.keep_trailing_newline = environment.keep_trailing_newline
|
||||||
|
|
||||||
|
# global lexing rules
|
||||||
|
self.rules = {
|
||||||
|
'root': [
|
||||||
|
# directives
|
||||||
|
(c('(.*?)(?:%s)' % '|'.join(
|
||||||
|
[r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % (
|
||||||
|
e(environment.block_start_string),
|
||||||
|
block_prefix_re,
|
||||||
|
e(environment.block_end_string),
|
||||||
|
e(environment.block_end_string)
|
||||||
|
)] + [
|
||||||
|
r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, prefix_re.get(n,r))
|
||||||
|
for n, r in root_tag_rules
|
||||||
|
])), (TOKEN_DATA, '#bygroup'), '#bygroup'),
|
||||||
|
# data
|
||||||
|
(c('.+'), TOKEN_DATA, None)
|
||||||
|
],
|
||||||
|
# comments
|
||||||
|
TOKEN_COMMENT_BEGIN: [
|
||||||
|
(c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
|
||||||
|
e(environment.comment_end_string),
|
||||||
|
e(environment.comment_end_string),
|
||||||
|
block_suffix_re
|
||||||
|
)), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'),
|
||||||
|
(c('(.)'), (Failure('Missing end of comment tag'),), None)
|
||||||
|
],
|
||||||
|
# blocks
|
||||||
|
TOKEN_BLOCK_BEGIN: [
|
||||||
|
(c('(?:\-%s\s*|%s)%s' % (
|
||||||
|
e(environment.block_end_string),
|
||||||
|
e(environment.block_end_string),
|
||||||
|
block_suffix_re
|
||||||
|
)), TOKEN_BLOCK_END, '#pop'),
|
||||||
|
] + tag_rules,
|
||||||
|
# variables
|
||||||
|
TOKEN_VARIABLE_BEGIN: [
|
||||||
|
(c('\-%s\s*|%s' % (
|
||||||
|
e(environment.variable_end_string),
|
||||||
|
e(environment.variable_end_string)
|
||||||
|
)), TOKEN_VARIABLE_END, '#pop')
|
||||||
|
] + tag_rules,
|
||||||
|
# raw block
|
||||||
|
TOKEN_RAW_BEGIN: [
|
||||||
|
(c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
|
||||||
|
e(environment.block_start_string),
|
||||||
|
block_prefix_re,
|
||||||
|
e(environment.block_end_string),
|
||||||
|
e(environment.block_end_string),
|
||||||
|
block_suffix_re
|
||||||
|
)), (TOKEN_DATA, TOKEN_RAW_END), '#pop'),
|
||||||
|
(c('(.)'), (Failure('Missing end of raw directive'),), None)
|
||||||
|
],
|
||||||
|
# line statements
|
||||||
|
TOKEN_LINESTATEMENT_BEGIN: [
|
||||||
|
(c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop')
|
||||||
|
] + tag_rules,
|
||||||
|
# line comments
|
||||||
|
TOKEN_LINECOMMENT_BEGIN: [
|
||||||
|
(c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT,
|
||||||
|
TOKEN_LINECOMMENT_END), '#pop')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _normalize_newlines(self, value):
|
||||||
|
"""Called for strings and template data to normalize it to unicode."""
|
||||||
|
return newline_re.sub(self.newline_sequence, value)
|
||||||
|
|
||||||
|
def tokenize(self, source, name=None, filename=None, state=None):
|
||||||
|
"""Calls tokeniter + tokenize and wraps it in a token stream.
|
||||||
|
"""
|
||||||
|
stream = self.tokeniter(source, name, filename, state)
|
||||||
|
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
||||||
|
|
||||||
|
def wrap(self, stream, name=None, filename=None):
|
||||||
|
"""This is called with the stream as returned by `tokenize` and wraps
|
||||||
|
every token in a :class:`Token` and converts the value.
|
||||||
|
"""
|
||||||
|
for lineno, token, value in stream:
|
||||||
|
if token in ignored_tokens:
|
||||||
|
continue
|
||||||
|
elif token == 'linestatement_begin':
|
||||||
|
token = 'block_begin'
|
||||||
|
elif token == 'linestatement_end':
|
||||||
|
token = 'block_end'
|
||||||
|
# we are not interested in those tokens in the parser
|
||||||
|
elif token in ('raw_begin', 'raw_end'):
|
||||||
|
continue
|
||||||
|
elif token == 'data':
|
||||||
|
value = self._normalize_newlines(value)
|
||||||
|
elif token == 'keyword':
|
||||||
|
token = value
|
||||||
|
elif token == 'name':
|
||||||
|
value = str(value)
|
||||||
|
elif token == 'string':
|
||||||
|
# try to unescape string
|
||||||
|
try:
|
||||||
|
value = self._normalize_newlines(value[1:-1]) \
|
||||||
|
.encode('ascii', 'backslashreplace') \
|
||||||
|
.decode('unicode-escape')
|
||||||
|
except Exception as e:
|
||||||
|
msg = str(e).split(':')[-1].strip()
|
||||||
|
raise TemplateSyntaxError(msg, lineno, name, filename)
|
||||||
|
# if we can express it as bytestring (ascii only)
|
||||||
|
# we do that for support of semi broken APIs
|
||||||
|
# as datetime.datetime.strftime. On python 3 this
|
||||||
|
# call becomes a noop thanks to 2to3
|
||||||
|
try:
|
||||||
|
value = str(value)
|
||||||
|
except UnicodeError:
|
||||||
|
pass
|
||||||
|
elif token == 'integer':
|
||||||
|
value = int(value)
|
||||||
|
elif token == 'float':
|
||||||
|
value = float(value)
|
||||||
|
elif token == 'operator':
|
||||||
|
token = operators[value]
|
||||||
|
yield Token(lineno, token, value)
|
||||||
|
|
||||||
|
def tokeniter(self, source, name, filename=None, state=None):
|
||||||
|
"""This method tokenizes the text and returns the tokens in a
|
||||||
|
generator. Use this method if you just want to tokenize a template.
|
||||||
|
"""
|
||||||
|
source = text_type(source)
|
||||||
|
lines = source.splitlines()
|
||||||
|
if self.keep_trailing_newline and source:
|
||||||
|
for newline in ('\r\n', '\r', '\n'):
|
||||||
|
if source.endswith(newline):
|
||||||
|
lines.append('')
|
||||||
|
break
|
||||||
|
source = '\n'.join(lines)
|
||||||
|
pos = 0
|
||||||
|
lineno = 1
|
||||||
|
stack = ['root']
|
||||||
|
if state is not None and state != 'root':
|
||||||
|
assert state in ('variable', 'block'), 'invalid state'
|
||||||
|
stack.append(state + '_begin')
|
||||||
|
else:
|
||||||
|
state = 'root'
|
||||||
|
statetokens = self.rules[stack[-1]]
|
||||||
|
source_length = len(source)
|
||||||
|
|
||||||
|
balancing_stack = []
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
# tokenizer loop
|
||||||
|
for regex, tokens, new_state in statetokens:
|
||||||
|
m = regex.match(source, pos)
|
||||||
|
# if no match we try again with the next rule
|
||||||
|
if m is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# we only match blocks and variables if braces / parentheses
|
||||||
|
# are balanced. continue parsing with the lower rule which
|
||||||
|
# is the operator rule. do this only if the end tags look
|
||||||
|
# like operators
|
||||||
|
if balancing_stack and \
|
||||||
|
tokens in ('variable_end', 'block_end',
|
||||||
|
'linestatement_end'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# tuples support more options
|
||||||
|
if isinstance(tokens, tuple):
|
||||||
|
for idx, token in enumerate(tokens):
|
||||||
|
# failure group
|
||||||
|
if token.__class__ is Failure:
|
||||||
|
raise token(lineno, filename)
|
||||||
|
# bygroup is a bit more complex, in that case we
|
||||||
|
# yield for the current token the first named
|
||||||
|
# group that matched
|
||||||
|
elif token == '#bygroup':
|
||||||
|
for key, value in iteritems(m.groupdict()):
|
||||||
|
if value is not None:
|
||||||
|
yield lineno, key, value
|
||||||
|
lineno += value.count('\n')
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise RuntimeError('%r wanted to resolve '
|
||||||
|
'the token dynamically'
|
||||||
|
' but no group matched'
|
||||||
|
% regex)
|
||||||
|
# normal group
|
||||||
|
else:
|
||||||
|
data = m.group(idx + 1)
|
||||||
|
if data or token not in ignore_if_empty:
|
||||||
|
yield lineno, token, data
|
||||||
|
lineno += data.count('\n')
|
||||||
|
|
||||||
|
# strings as token just are yielded as it.
|
||||||
|
else:
|
||||||
|
data = m.group()
|
||||||
|
# update brace/parentheses balance
|
||||||
|
if tokens == 'operator':
|
||||||
|
if data == '{':
|
||||||
|
balancing_stack.append('}')
|
||||||
|
elif data == '(':
|
||||||
|
balancing_stack.append(')')
|
||||||
|
elif data == '[':
|
||||||
|
balancing_stack.append(']')
|
||||||
|
elif data in ('}', ')', ']'):
|
||||||
|
if not balancing_stack:
|
||||||
|
raise TemplateSyntaxError('unexpected \'%s\'' %
|
||||||
|
data, lineno, name,
|
||||||
|
filename)
|
||||||
|
expected_op = balancing_stack.pop()
|
||||||
|
if expected_op != data:
|
||||||
|
raise TemplateSyntaxError('unexpected \'%s\', '
|
||||||
|
'expected \'%s\'' %
|
||||||
|
(data, expected_op),
|
||||||
|
lineno, name,
|
||||||
|
filename)
|
||||||
|
# yield items
|
||||||
|
if data or tokens not in ignore_if_empty:
|
||||||
|
yield lineno, tokens, data
|
||||||
|
lineno += data.count('\n')
|
||||||
|
|
||||||
|
# fetch new position into new variable so that we can check
|
||||||
|
# if there is a internal parsing error which would result
|
||||||
|
# in an infinite loop
|
||||||
|
pos2 = m.end()
|
||||||
|
|
||||||
|
# handle state changes
|
||||||
|
if new_state is not None:
|
||||||
|
# remove the uppermost state
|
||||||
|
if new_state == '#pop':
|
||||||
|
stack.pop()
|
||||||
|
# resolve the new state by group checking
|
||||||
|
elif new_state == '#bygroup':
|
||||||
|
for key, value in iteritems(m.groupdict()):
|
||||||
|
if value is not None:
|
||||||
|
stack.append(key)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise RuntimeError('%r wanted to resolve the '
|
||||||
|
'new state dynamically but'
|
||||||
|
' no group matched' %
|
||||||
|
regex)
|
||||||
|
# direct state name given
|
||||||
|
else:
|
||||||
|
stack.append(new_state)
|
||||||
|
statetokens = self.rules[stack[-1]]
|
||||||
|
# we are still at the same position and no stack change.
|
||||||
|
# this means a loop without break condition, avoid that and
|
||||||
|
# raise error
|
||||||
|
elif pos2 == pos:
|
||||||
|
raise RuntimeError('%r yielded empty string without '
|
||||||
|
'stack change' % regex)
|
||||||
|
# publish new function and start again
|
||||||
|
pos = pos2
|
||||||
|
break
|
||||||
|
# if loop terminated without break we haven't found a single match
|
||||||
|
# either we are at the end of the file or we have a problem
|
||||||
|
else:
|
||||||
|
# end of text
|
||||||
|
if pos >= source_length:
|
||||||
|
return
|
||||||
|
# something went wrong
|
||||||
|
raise TemplateSyntaxError('unexpected char %r at %d' %
|
||||||
|
(source[pos], pos), lineno,
|
||||||
|
name, filename)
|
471
tools/external/jinja2/loaders.py
vendored
Normal file
471
tools/external/jinja2/loaders.py
vendored
Normal file
|
@ -0,0 +1,471 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.loaders
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Jinja loader classes.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import weakref
|
||||||
|
from types import ModuleType
|
||||||
|
from os import path
|
||||||
|
from hashlib import sha1
|
||||||
|
from jinja2.exceptions import TemplateNotFound
|
||||||
|
from jinja2.utils import open_if_exists, internalcode
|
||||||
|
from jinja2._compat import string_types, iteritems
|
||||||
|
|
||||||
|
|
||||||
|
def split_template_path(template):
|
||||||
|
"""Split a path into segments and perform a sanity check. If it detects
|
||||||
|
'..' in the path it will raise a `TemplateNotFound` error.
|
||||||
|
"""
|
||||||
|
pieces = []
|
||||||
|
for piece in template.split('/'):
|
||||||
|
if path.sep in piece \
|
||||||
|
or (path.altsep and path.altsep in piece) or \
|
||||||
|
piece == path.pardir:
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
elif piece and piece != '.':
|
||||||
|
pieces.append(piece)
|
||||||
|
return pieces
|
||||||
|
|
||||||
|
|
||||||
|
class BaseLoader(object):
|
||||||
|
"""Baseclass for all loaders. Subclass this and override `get_source` to
|
||||||
|
implement a custom loading mechanism. The environment provides a
|
||||||
|
`get_template` method that calls the loader's `load` method to get the
|
||||||
|
:class:`Template` object.
|
||||||
|
|
||||||
|
A very basic example for a loader that looks up templates on the file
|
||||||
|
system could look like this::
|
||||||
|
|
||||||
|
from jinja2 import BaseLoader, TemplateNotFound
|
||||||
|
from os.path import join, exists, getmtime
|
||||||
|
|
||||||
|
class MyLoader(BaseLoader):
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
path = join(self.path, template)
|
||||||
|
if not exists(path):
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
mtime = getmtime(path)
|
||||||
|
with file(path) as f:
|
||||||
|
source = f.read().decode('utf-8')
|
||||||
|
return source, path, lambda: mtime == getmtime(path)
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: if set to `False` it indicates that the loader cannot provide access
|
||||||
|
#: to the source of templates.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.4
|
||||||
|
has_source_access = True
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
"""Get the template source, filename and reload helper for a template.
|
||||||
|
It's passed the environment and template name and has to return a
|
||||||
|
tuple in the form ``(source, filename, uptodate)`` or raise a
|
||||||
|
`TemplateNotFound` error if it can't locate the template.
|
||||||
|
|
||||||
|
The source part of the returned tuple must be the source of the
|
||||||
|
template as unicode string or a ASCII bytestring. The filename should
|
||||||
|
be the name of the file on the filesystem if it was loaded from there,
|
||||||
|
otherwise `None`. The filename is used by python for the tracebacks
|
||||||
|
if no loader extension is used.
|
||||||
|
|
||||||
|
The last item in the tuple is the `uptodate` function. If auto
|
||||||
|
reloading is enabled it's always called to check if the template
|
||||||
|
changed. No arguments are passed so the function must store the
|
||||||
|
old state somewhere (for example in a closure). If it returns `False`
|
||||||
|
the template will be reloaded.
|
||||||
|
"""
|
||||||
|
if not self.has_source_access:
|
||||||
|
raise RuntimeError('%s cannot provide access to the source' %
|
||||||
|
self.__class__.__name__)
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
"""Iterates over all templates. If the loader does not support that
|
||||||
|
it should raise a :exc:`TypeError` which is the default behavior.
|
||||||
|
"""
|
||||||
|
raise TypeError('this loader cannot iterate over all templates')
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(self, environment, name, globals=None):
|
||||||
|
"""Loads a template. This method looks up the template in the cache
|
||||||
|
or loads one by calling :meth:`get_source`. Subclasses should not
|
||||||
|
override this method as loaders working on collections of other
|
||||||
|
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
|
||||||
|
will not call this method but `get_source` directly.
|
||||||
|
"""
|
||||||
|
code = None
|
||||||
|
if globals is None:
|
||||||
|
globals = {}
|
||||||
|
|
||||||
|
# first we try to get the source for this template together
|
||||||
|
# with the filename and the uptodate function.
|
||||||
|
source, filename, uptodate = self.get_source(environment, name)
|
||||||
|
|
||||||
|
# try to load the code from the bytecode cache if there is a
|
||||||
|
# bytecode cache configured.
|
||||||
|
bcc = environment.bytecode_cache
|
||||||
|
if bcc is not None:
|
||||||
|
bucket = bcc.get_bucket(environment, name, filename, source)
|
||||||
|
code = bucket.code
|
||||||
|
|
||||||
|
# if we don't have code so far (not cached, no longer up to
|
||||||
|
# date) etc. we compile the template
|
||||||
|
if code is None:
|
||||||
|
code = environment.compile(source, name, filename)
|
||||||
|
|
||||||
|
# if the bytecode cache is available and the bucket doesn't
|
||||||
|
# have a code so far, we give the bucket the new code and put
|
||||||
|
# it back to the bytecode cache.
|
||||||
|
if bcc is not None and bucket.code is None:
|
||||||
|
bucket.code = code
|
||||||
|
bcc.set_bucket(bucket)
|
||||||
|
|
||||||
|
return environment.template_class.from_code(environment, code,
|
||||||
|
globals, uptodate)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemLoader(BaseLoader):
|
||||||
|
"""Loads templates from the file system. This loader can find templates
|
||||||
|
in folders on the file system and is the preferred way to load them.
|
||||||
|
|
||||||
|
The loader takes the path to the templates as string, or if multiple
|
||||||
|
locations are wanted a list of them which is then looked up in the
|
||||||
|
given order:
|
||||||
|
|
||||||
|
>>> loader = FileSystemLoader('/path/to/templates')
|
||||||
|
>>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
|
||||||
|
|
||||||
|
Per default the template encoding is ``'utf-8'`` which can be changed
|
||||||
|
by setting the `encoding` parameter to something else.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, searchpath, encoding='utf-8'):
|
||||||
|
if isinstance(searchpath, string_types):
|
||||||
|
searchpath = [searchpath]
|
||||||
|
self.searchpath = list(searchpath)
|
||||||
|
self.encoding = encoding
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
pieces = split_template_path(template)
|
||||||
|
for searchpath in self.searchpath:
|
||||||
|
filename = path.join(searchpath, *pieces)
|
||||||
|
f = open_if_exists(filename)
|
||||||
|
if f is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
contents = f.read().decode(self.encoding)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
mtime = path.getmtime(filename)
|
||||||
|
def uptodate():
|
||||||
|
try:
|
||||||
|
return path.getmtime(filename) == mtime
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return contents, filename, uptodate
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
found = set()
|
||||||
|
for searchpath in self.searchpath:
|
||||||
|
for dirpath, dirnames, filenames in os.walk(searchpath):
|
||||||
|
for filename in filenames:
|
||||||
|
template = os.path.join(dirpath, filename) \
|
||||||
|
[len(searchpath):].strip(os.path.sep) \
|
||||||
|
.replace(os.path.sep, '/')
|
||||||
|
if template[:2] == './':
|
||||||
|
template = template[2:]
|
||||||
|
if template not in found:
|
||||||
|
found.add(template)
|
||||||
|
return sorted(found)
|
||||||
|
|
||||||
|
|
||||||
|
class PackageLoader(BaseLoader):
|
||||||
|
"""Load templates from python eggs or packages. It is constructed with
|
||||||
|
the name of the python package and the path to the templates in that
|
||||||
|
package::
|
||||||
|
|
||||||
|
loader = PackageLoader('mypackage', 'views')
|
||||||
|
|
||||||
|
If the package path is not given, ``'templates'`` is assumed.
|
||||||
|
|
||||||
|
Per default the template encoding is ``'utf-8'`` which can be changed
|
||||||
|
by setting the `encoding` parameter to something else. Due to the nature
|
||||||
|
of eggs it's only possible to reload templates if the package was loaded
|
||||||
|
from the file system and not a zip file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, package_name, package_path='templates',
|
||||||
|
encoding='utf-8'):
|
||||||
|
from pkg_resources import DefaultProvider, ResourceManager, \
|
||||||
|
get_provider
|
||||||
|
provider = get_provider(package_name)
|
||||||
|
self.encoding = encoding
|
||||||
|
self.manager = ResourceManager()
|
||||||
|
self.filesystem_bound = isinstance(provider, DefaultProvider)
|
||||||
|
self.provider = provider
|
||||||
|
self.package_path = package_path
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
pieces = split_template_path(template)
|
||||||
|
p = '/'.join((self.package_path,) + tuple(pieces))
|
||||||
|
if not self.provider.has_resource(p):
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
filename = uptodate = None
|
||||||
|
if self.filesystem_bound:
|
||||||
|
filename = self.provider.get_resource_filename(self.manager, p)
|
||||||
|
mtime = path.getmtime(filename)
|
||||||
|
def uptodate():
|
||||||
|
try:
|
||||||
|
return path.getmtime(filename) == mtime
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
source = self.provider.get_resource_string(self.manager, p)
|
||||||
|
return source.decode(self.encoding), filename, uptodate
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
path = self.package_path
|
||||||
|
if path[:2] == './':
|
||||||
|
path = path[2:]
|
||||||
|
elif path == '.':
|
||||||
|
path = ''
|
||||||
|
offset = len(path)
|
||||||
|
results = []
|
||||||
|
def _walk(path):
|
||||||
|
for filename in self.provider.resource_listdir(path):
|
||||||
|
fullname = path + '/' + filename
|
||||||
|
if self.provider.resource_isdir(fullname):
|
||||||
|
_walk(fullname)
|
||||||
|
else:
|
||||||
|
results.append(fullname[offset:].lstrip('/'))
|
||||||
|
_walk(path)
|
||||||
|
results.sort()
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
class DictLoader(BaseLoader):
|
||||||
|
"""Loads a template from a python dict. It's passed a dict of unicode
|
||||||
|
strings bound to template names. This loader is useful for unittesting:
|
||||||
|
|
||||||
|
>>> loader = DictLoader({'index.html': 'source here'})
|
||||||
|
|
||||||
|
Because auto reloading is rarely useful this is disabled per default.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, mapping):
|
||||||
|
self.mapping = mapping
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
if template in self.mapping:
|
||||||
|
source = self.mapping[template]
|
||||||
|
return source, None, lambda: source == self.mapping.get(template)
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
return sorted(self.mapping)
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionLoader(BaseLoader):
|
||||||
|
"""A loader that is passed a function which does the loading. The
|
||||||
|
function becomes the name of the template passed and has to return either
|
||||||
|
an unicode string with the template source, a tuple in the form ``(source,
|
||||||
|
filename, uptodatefunc)`` or `None` if the template does not exist.
|
||||||
|
|
||||||
|
>>> def load_template(name):
|
||||||
|
... if name == 'index.html':
|
||||||
|
... return '...'
|
||||||
|
...
|
||||||
|
>>> loader = FunctionLoader(load_template)
|
||||||
|
|
||||||
|
The `uptodatefunc` is a function that is called if autoreload is enabled
|
||||||
|
and has to return `True` if the template is still up to date. For more
|
||||||
|
details have a look at :meth:`BaseLoader.get_source` which has the same
|
||||||
|
return value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, load_func):
|
||||||
|
self.load_func = load_func
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
rv = self.load_func(template)
|
||||||
|
if rv is None:
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
elif isinstance(rv, string_types):
|
||||||
|
return rv, None, None
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixLoader(BaseLoader):
|
||||||
|
"""A loader that is passed a dict of loaders where each loader is bound
|
||||||
|
to a prefix. The prefix is delimited from the template by a slash per
|
||||||
|
default, which can be changed by setting the `delimiter` argument to
|
||||||
|
something else::
|
||||||
|
|
||||||
|
loader = PrefixLoader({
|
||||||
|
'app1': PackageLoader('mypackage.app1'),
|
||||||
|
'app2': PackageLoader('mypackage.app2')
|
||||||
|
})
|
||||||
|
|
||||||
|
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
|
||||||
|
by loading ``'app2/index.html'`` the file from the second.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, mapping, delimiter='/'):
|
||||||
|
self.mapping = mapping
|
||||||
|
self.delimiter = delimiter
|
||||||
|
|
||||||
|
def get_loader(self, template):
|
||||||
|
try:
|
||||||
|
prefix, name = template.split(self.delimiter, 1)
|
||||||
|
loader = self.mapping[prefix]
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
return loader, name
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
loader, name = self.get_loader(template)
|
||||||
|
try:
|
||||||
|
return loader.get_source(environment, name)
|
||||||
|
except TemplateNotFound:
|
||||||
|
# re-raise the exception with the correct fileame here.
|
||||||
|
# (the one that includes the prefix)
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(self, environment, name, globals=None):
|
||||||
|
loader, local_name = self.get_loader(name)
|
||||||
|
try:
|
||||||
|
return loader.load(environment, local_name, globals)
|
||||||
|
except TemplateNotFound:
|
||||||
|
# re-raise the exception with the correct fileame here.
|
||||||
|
# (the one that includes the prefix)
|
||||||
|
raise TemplateNotFound(name)
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
result = []
|
||||||
|
for prefix, loader in iteritems(self.mapping):
|
||||||
|
for template in loader.list_templates():
|
||||||
|
result.append(prefix + self.delimiter + template)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceLoader(BaseLoader):
|
||||||
|
"""This loader works like the `PrefixLoader` just that no prefix is
|
||||||
|
specified. If a template could not be found by one loader the next one
|
||||||
|
is tried.
|
||||||
|
|
||||||
|
>>> loader = ChoiceLoader([
|
||||||
|
... FileSystemLoader('/path/to/user/templates'),
|
||||||
|
... FileSystemLoader('/path/to/system/templates')
|
||||||
|
... ])
|
||||||
|
|
||||||
|
This is useful if you want to allow users to override builtin templates
|
||||||
|
from a different location.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, loaders):
|
||||||
|
self.loaders = loaders
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
for loader in self.loaders:
|
||||||
|
try:
|
||||||
|
return loader.get_source(environment, template)
|
||||||
|
except TemplateNotFound:
|
||||||
|
pass
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(self, environment, name, globals=None):
|
||||||
|
for loader in self.loaders:
|
||||||
|
try:
|
||||||
|
return loader.load(environment, name, globals)
|
||||||
|
except TemplateNotFound:
|
||||||
|
pass
|
||||||
|
raise TemplateNotFound(name)
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
found = set()
|
||||||
|
for loader in self.loaders:
|
||||||
|
found.update(loader.list_templates())
|
||||||
|
return sorted(found)
|
||||||
|
|
||||||
|
|
||||||
|
class _TemplateModule(ModuleType):
|
||||||
|
"""Like a normal module but with support for weak references"""
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleLoader(BaseLoader):
|
||||||
|
"""This loader loads templates from precompiled templates.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
>>> loader = ChoiceLoader([
|
||||||
|
... ModuleLoader('/path/to/compiled/templates'),
|
||||||
|
... FileSystemLoader('/path/to/templates')
|
||||||
|
... ])
|
||||||
|
|
||||||
|
Templates can be precompiled with :meth:`Environment.compile_templates`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
has_source_access = False
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
package_name = '_jinja2_module_templates_%x' % id(self)
|
||||||
|
|
||||||
|
# create a fake module that looks for the templates in the
|
||||||
|
# path given.
|
||||||
|
mod = _TemplateModule(package_name)
|
||||||
|
if isinstance(path, string_types):
|
||||||
|
path = [path]
|
||||||
|
else:
|
||||||
|
path = list(path)
|
||||||
|
mod.__path__ = path
|
||||||
|
|
||||||
|
sys.modules[package_name] = weakref.proxy(mod,
|
||||||
|
lambda x: sys.modules.pop(package_name, None))
|
||||||
|
|
||||||
|
# the only strong reference, the sys.modules entry is weak
|
||||||
|
# so that the garbage collector can remove it once the
|
||||||
|
# loader that created it goes out of business.
|
||||||
|
self.module = mod
|
||||||
|
self.package_name = package_name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_template_key(name):
|
||||||
|
return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_module_filename(name):
|
||||||
|
return ModuleLoader.get_template_key(name) + '.py'
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(self, environment, name, globals=None):
|
||||||
|
key = self.get_template_key(name)
|
||||||
|
module = '%s.%s' % (self.package_name, key)
|
||||||
|
mod = getattr(self.module, module, None)
|
||||||
|
if mod is None:
|
||||||
|
try:
|
||||||
|
mod = __import__(module, None, None, ['root'])
|
||||||
|
except ImportError:
|
||||||
|
raise TemplateNotFound(name)
|
||||||
|
|
||||||
|
# remove the entry from sys.modules, we only want the attribute
|
||||||
|
# on the module object we have stored on the loader.
|
||||||
|
sys.modules.pop(module, None)
|
||||||
|
|
||||||
|
return environment.template_class.from_module_dict(
|
||||||
|
environment, mod.__dict__, globals)
|
103
tools/external/jinja2/meta.py
vendored
Normal file
103
tools/external/jinja2/meta.py
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.meta
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module implements various functions that exposes information about
|
||||||
|
templates that might be interesting for various kinds of applications.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from jinja2 import nodes
|
||||||
|
from jinja2.compiler import CodeGenerator
|
||||||
|
from jinja2._compat import string_types
|
||||||
|
|
||||||
|
|
||||||
|
class TrackingCodeGenerator(CodeGenerator):
|
||||||
|
"""We abuse the code generator for introspection."""
|
||||||
|
|
||||||
|
def __init__(self, environment):
|
||||||
|
CodeGenerator.__init__(self, environment, '<introspection>',
|
||||||
|
'<introspection>')
|
||||||
|
self.undeclared_identifiers = set()
|
||||||
|
|
||||||
|
def write(self, x):
|
||||||
|
"""Don't write."""
|
||||||
|
|
||||||
|
def pull_locals(self, frame):
|
||||||
|
"""Remember all undeclared identifiers."""
|
||||||
|
self.undeclared_identifiers.update(frame.identifiers.undeclared)
|
||||||
|
|
||||||
|
|
||||||
|
def find_undeclared_variables(ast):
|
||||||
|
"""Returns a set of all variables in the AST that will be looked up from
|
||||||
|
the context at runtime. Because at compile time it's not known which
|
||||||
|
variables will be used depending on the path the execution takes at
|
||||||
|
runtime, all variables are returned.
|
||||||
|
|
||||||
|
>>> from jinja2 import Environment, meta
|
||||||
|
>>> env = Environment()
|
||||||
|
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
||||||
|
>>> meta.find_undeclared_variables(ast)
|
||||||
|
set(['bar'])
|
||||||
|
|
||||||
|
.. admonition:: Implementation
|
||||||
|
|
||||||
|
Internally the code generator is used for finding undeclared variables.
|
||||||
|
This is good to know because the code generator might raise a
|
||||||
|
:exc:`TemplateAssertionError` during compilation and as a matter of
|
||||||
|
fact this function can currently raise that exception as well.
|
||||||
|
"""
|
||||||
|
codegen = TrackingCodeGenerator(ast.environment)
|
||||||
|
codegen.visit(ast)
|
||||||
|
return codegen.undeclared_identifiers
|
||||||
|
|
||||||
|
|
||||||
|
def find_referenced_templates(ast):
|
||||||
|
"""Finds all the referenced templates from the AST. This will return an
|
||||||
|
iterator over all the hardcoded template extensions, inclusions and
|
||||||
|
imports. If dynamic inheritance or inclusion is used, `None` will be
|
||||||
|
yielded.
|
||||||
|
|
||||||
|
>>> from jinja2 import Environment, meta
|
||||||
|
>>> env = Environment()
|
||||||
|
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
||||||
|
>>> list(meta.find_referenced_templates(ast))
|
||||||
|
['layout.html', None]
|
||||||
|
|
||||||
|
This function is useful for dependency tracking. For example if you want
|
||||||
|
to rebuild parts of the website after a layout template has changed.
|
||||||
|
"""
|
||||||
|
for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
|
||||||
|
nodes.Include)):
|
||||||
|
if not isinstance(node.template, nodes.Const):
|
||||||
|
# a tuple with some non consts in there
|
||||||
|
if isinstance(node.template, (nodes.Tuple, nodes.List)):
|
||||||
|
for template_name in node.template.items:
|
||||||
|
# something const, only yield the strings and ignore
|
||||||
|
# non-string consts that really just make no sense
|
||||||
|
if isinstance(template_name, nodes.Const):
|
||||||
|
if isinstance(template_name.value, string_types):
|
||||||
|
yield template_name.value
|
||||||
|
# something dynamic in there
|
||||||
|
else:
|
||||||
|
yield None
|
||||||
|
# something dynamic we don't know about here
|
||||||
|
else:
|
||||||
|
yield None
|
||||||
|
continue
|
||||||
|
# constant is a basestring, direct template name
|
||||||
|
if isinstance(node.template.value, string_types):
|
||||||
|
yield node.template.value
|
||||||
|
# a tuple or list (latter *should* not happen) made of consts,
|
||||||
|
# yield the consts that are strings. We could warn here for
|
||||||
|
# non string values
|
||||||
|
elif isinstance(node, nodes.Include) and \
|
||||||
|
isinstance(node.template.value, (tuple, list)):
|
||||||
|
for template_name in node.template.value:
|
||||||
|
if isinstance(template_name, string_types):
|
||||||
|
yield template_name
|
||||||
|
# something else we don't care about, we could warn here
|
||||||
|
else:
|
||||||
|
yield None
|
914
tools/external/jinja2/nodes.py
vendored
Normal file
914
tools/external/jinja2/nodes.py
vendored
Normal file
|
@ -0,0 +1,914 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.nodes
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module implements additional nodes derived from the ast base node.
|
||||||
|
|
||||||
|
It also provides some node tree helper functions like `in_lineno` and
|
||||||
|
`get_nodes` used by the parser and translator in order to normalize
|
||||||
|
python and jinja nodes.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import operator
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from jinja2.utils import Markup
|
||||||
|
from jinja2._compat import next, izip, with_metaclass, text_type, \
|
||||||
|
method_type, function_type
|
||||||
|
|
||||||
|
|
||||||
|
#: the types we support for context functions
|
||||||
|
_context_function_types = (function_type, method_type)
|
||||||
|
|
||||||
|
|
||||||
|
_binop_to_func = {
|
||||||
|
'*': operator.mul,
|
||||||
|
'/': operator.truediv,
|
||||||
|
'//': operator.floordiv,
|
||||||
|
'**': operator.pow,
|
||||||
|
'%': operator.mod,
|
||||||
|
'+': operator.add,
|
||||||
|
'-': operator.sub
|
||||||
|
}
|
||||||
|
|
||||||
|
_uaop_to_func = {
|
||||||
|
'not': operator.not_,
|
||||||
|
'+': operator.pos,
|
||||||
|
'-': operator.neg
|
||||||
|
}
|
||||||
|
|
||||||
|
_cmpop_to_func = {
|
||||||
|
'eq': operator.eq,
|
||||||
|
'ne': operator.ne,
|
||||||
|
'gt': operator.gt,
|
||||||
|
'gteq': operator.ge,
|
||||||
|
'lt': operator.lt,
|
||||||
|
'lteq': operator.le,
|
||||||
|
'in': lambda a, b: a in b,
|
||||||
|
'notin': lambda a, b: a not in b
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Impossible(Exception):
|
||||||
|
"""Raised if the node could not perform a requested action."""
|
||||||
|
|
||||||
|
|
||||||
|
class NodeType(type):
|
||||||
|
"""A metaclass for nodes that handles the field and attribute
|
||||||
|
inheritance. fields and attributes from the parent class are
|
||||||
|
automatically forwarded to the child."""
|
||||||
|
|
||||||
|
def __new__(cls, name, bases, d):
|
||||||
|
for attr in 'fields', 'attributes':
|
||||||
|
storage = []
|
||||||
|
storage.extend(getattr(bases[0], attr, ()))
|
||||||
|
storage.extend(d.get(attr, ()))
|
||||||
|
assert len(bases) == 1, 'multiple inheritance not allowed'
|
||||||
|
assert len(storage) == len(set(storage)), 'layout conflict'
|
||||||
|
d[attr] = tuple(storage)
|
||||||
|
d.setdefault('abstract', False)
|
||||||
|
return type.__new__(cls, name, bases, d)
|
||||||
|
|
||||||
|
|
||||||
|
class EvalContext(object):
|
||||||
|
"""Holds evaluation time information. Custom attributes can be attached
|
||||||
|
to it in extensions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, environment, template_name=None):
|
||||||
|
self.environment = environment
|
||||||
|
if callable(environment.autoescape):
|
||||||
|
self.autoescape = environment.autoescape(template_name)
|
||||||
|
else:
|
||||||
|
self.autoescape = environment.autoescape
|
||||||
|
self.volatile = False
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
return self.__dict__.copy()
|
||||||
|
|
||||||
|
def revert(self, old):
|
||||||
|
self.__dict__.clear()
|
||||||
|
self.__dict__.update(old)
|
||||||
|
|
||||||
|
|
||||||
|
def get_eval_context(node, ctx):
|
||||||
|
if ctx is None:
|
||||||
|
if node.environment is None:
|
||||||
|
raise RuntimeError('if no eval context is passed, the '
|
||||||
|
'node must have an attached '
|
||||||
|
'environment.')
|
||||||
|
return EvalContext(node.environment)
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class Node(with_metaclass(NodeType, object)):
|
||||||
|
"""Baseclass for all Jinja2 nodes. There are a number of nodes available
|
||||||
|
of different types. There are four major types:
|
||||||
|
|
||||||
|
- :class:`Stmt`: statements
|
||||||
|
- :class:`Expr`: expressions
|
||||||
|
- :class:`Helper`: helper nodes
|
||||||
|
- :class:`Template`: the outermost wrapper node
|
||||||
|
|
||||||
|
All nodes have fields and attributes. Fields may be other nodes, lists,
|
||||||
|
or arbitrary values. Fields are passed to the constructor as regular
|
||||||
|
positional arguments, attributes as keyword arguments. Each node has
|
||||||
|
two attributes: `lineno` (the line number of the node) and `environment`.
|
||||||
|
The `environment` attribute is set at the end of the parsing process for
|
||||||
|
all nodes automatically.
|
||||||
|
"""
|
||||||
|
fields = ()
|
||||||
|
attributes = ('lineno', 'environment')
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def __init__(self, *fields, **attributes):
|
||||||
|
if self.abstract:
|
||||||
|
raise TypeError('abstract nodes are not instanciable')
|
||||||
|
if fields:
|
||||||
|
if len(fields) != len(self.fields):
|
||||||
|
if not self.fields:
|
||||||
|
raise TypeError('%r takes 0 arguments' %
|
||||||
|
self.__class__.__name__)
|
||||||
|
raise TypeError('%r takes 0 or %d argument%s' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
len(self.fields),
|
||||||
|
len(self.fields) != 1 and 's' or ''
|
||||||
|
))
|
||||||
|
for name, arg in izip(self.fields, fields):
|
||||||
|
setattr(self, name, arg)
|
||||||
|
for attr in self.attributes:
|
||||||
|
setattr(self, attr, attributes.pop(attr, None))
|
||||||
|
if attributes:
|
||||||
|
raise TypeError('unknown attribute %r' %
|
||||||
|
next(iter(attributes)))
|
||||||
|
|
||||||
|
def iter_fields(self, exclude=None, only=None):
|
||||||
|
"""This method iterates over all fields that are defined and yields
|
||||||
|
``(key, value)`` tuples. Per default all fields are returned, but
|
||||||
|
it's possible to limit that to some fields by providing the `only`
|
||||||
|
parameter or to exclude some using the `exclude` parameter. Both
|
||||||
|
should be sets or tuples of field names.
|
||||||
|
"""
|
||||||
|
for name in self.fields:
|
||||||
|
if (exclude is only is None) or \
|
||||||
|
(exclude is not None and name not in exclude) or \
|
||||||
|
(only is not None and name in only):
|
||||||
|
try:
|
||||||
|
yield name, getattr(self, name)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def iter_child_nodes(self, exclude=None, only=None):
|
||||||
|
"""Iterates over all direct child nodes of the node. This iterates
|
||||||
|
over all fields and yields the values of they are nodes. If the value
|
||||||
|
of a field is a list all the nodes in that list are returned.
|
||||||
|
"""
|
||||||
|
for field, item in self.iter_fields(exclude, only):
|
||||||
|
if isinstance(item, list):
|
||||||
|
for n in item:
|
||||||
|
if isinstance(n, Node):
|
||||||
|
yield n
|
||||||
|
elif isinstance(item, Node):
|
||||||
|
yield item
|
||||||
|
|
||||||
|
def find(self, node_type):
|
||||||
|
"""Find the first node of a given type. If no such node exists the
|
||||||
|
return value is `None`.
|
||||||
|
"""
|
||||||
|
for result in self.find_all(node_type):
|
||||||
|
return result
|
||||||
|
|
||||||
|
def find_all(self, node_type):
|
||||||
|
"""Find all the nodes of a given type. If the type is a tuple,
|
||||||
|
the check is performed for any of the tuple items.
|
||||||
|
"""
|
||||||
|
for child in self.iter_child_nodes():
|
||||||
|
if isinstance(child, node_type):
|
||||||
|
yield child
|
||||||
|
for result in child.find_all(node_type):
|
||||||
|
yield result
|
||||||
|
|
||||||
|
def set_ctx(self, ctx):
|
||||||
|
"""Reset the context of a node and all child nodes. Per default the
|
||||||
|
parser will all generate nodes that have a 'load' context as it's the
|
||||||
|
most common one. This method is used in the parser to set assignment
|
||||||
|
targets and other nodes to a store context.
|
||||||
|
"""
|
||||||
|
todo = deque([self])
|
||||||
|
while todo:
|
||||||
|
node = todo.popleft()
|
||||||
|
if 'ctx' in node.fields:
|
||||||
|
node.ctx = ctx
|
||||||
|
todo.extend(node.iter_child_nodes())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_lineno(self, lineno, override=False):
|
||||||
|
"""Set the line numbers of the node and children."""
|
||||||
|
todo = deque([self])
|
||||||
|
while todo:
|
||||||
|
node = todo.popleft()
|
||||||
|
if 'lineno' in node.attributes:
|
||||||
|
if node.lineno is None or override:
|
||||||
|
node.lineno = lineno
|
||||||
|
todo.extend(node.iter_child_nodes())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_environment(self, environment):
|
||||||
|
"""Set the environment for all nodes."""
|
||||||
|
todo = deque([self])
|
||||||
|
while todo:
|
||||||
|
node = todo.popleft()
|
||||||
|
node.environment = environment
|
||||||
|
todo.extend(node.iter_child_nodes())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return type(self) is type(other) and \
|
||||||
|
tuple(self.iter_fields()) == tuple(other.iter_fields())
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
# Restore Python 2 hashing behavior on Python 3
|
||||||
|
__hash__ = object.__hash__
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '%s(%s)' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
|
||||||
|
arg in self.fields)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Stmt(Node):
|
||||||
|
"""Base node for all statements."""
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class Helper(Node):
|
||||||
|
"""Nodes that exist in a specific context only."""
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class Template(Node):
|
||||||
|
"""Node that represents a template. This must be the outermost node that
|
||||||
|
is passed to the compiler.
|
||||||
|
"""
|
||||||
|
fields = ('body',)
|
||||||
|
|
||||||
|
|
||||||
|
class Output(Stmt):
|
||||||
|
"""A node that holds multiple expressions which are then printed out.
|
||||||
|
This is used both for the `print` statement and the regular template data.
|
||||||
|
"""
|
||||||
|
fields = ('nodes',)
|
||||||
|
|
||||||
|
|
||||||
|
class Extends(Stmt):
|
||||||
|
"""Represents an extends statement."""
|
||||||
|
fields = ('template',)
|
||||||
|
|
||||||
|
|
||||||
|
class For(Stmt):
|
||||||
|
"""The for loop. `target` is the target for the iteration (usually a
|
||||||
|
:class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list
|
||||||
|
of nodes that are used as loop-body, and `else_` a list of nodes for the
|
||||||
|
`else` block. If no else node exists it has to be an empty list.
|
||||||
|
|
||||||
|
For filtered nodes an expression can be stored as `test`, otherwise `None`.
|
||||||
|
"""
|
||||||
|
fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive')
|
||||||
|
|
||||||
|
|
||||||
|
class If(Stmt):
|
||||||
|
"""If `test` is true, `body` is rendered, else `else_`."""
|
||||||
|
fields = ('test', 'body', 'else_')
|
||||||
|
|
||||||
|
|
||||||
|
class Macro(Stmt):
|
||||||
|
"""A macro definition. `name` is the name of the macro, `args` a list of
|
||||||
|
arguments and `defaults` a list of defaults if there are any. `body` is
|
||||||
|
a list of nodes for the macro body.
|
||||||
|
"""
|
||||||
|
fields = ('name', 'args', 'defaults', 'body')
|
||||||
|
|
||||||
|
|
||||||
|
class CallBlock(Stmt):
|
||||||
|
"""Like a macro without a name but a call instead. `call` is called with
|
||||||
|
the unnamed macro as `caller` argument this node holds.
|
||||||
|
"""
|
||||||
|
fields = ('call', 'args', 'defaults', 'body')
|
||||||
|
|
||||||
|
|
||||||
|
class FilterBlock(Stmt):
|
||||||
|
"""Node for filter sections."""
|
||||||
|
fields = ('body', 'filter')
|
||||||
|
|
||||||
|
|
||||||
|
class Block(Stmt):
|
||||||
|
"""A node that represents a block."""
|
||||||
|
fields = ('name', 'body', 'scoped')
|
||||||
|
|
||||||
|
|
||||||
|
class Include(Stmt):
|
||||||
|
"""A node that represents the include tag."""
|
||||||
|
fields = ('template', 'with_context', 'ignore_missing')
|
||||||
|
|
||||||
|
|
||||||
|
class Import(Stmt):
|
||||||
|
"""A node that represents the import tag."""
|
||||||
|
fields = ('template', 'target', 'with_context')
|
||||||
|
|
||||||
|
|
||||||
|
class FromImport(Stmt):
|
||||||
|
"""A node that represents the from import tag. It's important to not
|
||||||
|
pass unsafe names to the name attribute. The compiler translates the
|
||||||
|
attribute lookups directly into getattr calls and does *not* use the
|
||||||
|
subscript callback of the interface. As exported variables may not
|
||||||
|
start with double underscores (which the parser asserts) this is not a
|
||||||
|
problem for regular Jinja code, but if this node is used in an extension
|
||||||
|
extra care must be taken.
|
||||||
|
|
||||||
|
The list of names may contain tuples if aliases are wanted.
|
||||||
|
"""
|
||||||
|
fields = ('template', 'names', 'with_context')
|
||||||
|
|
||||||
|
|
||||||
|
class ExprStmt(Stmt):
|
||||||
|
"""A statement that evaluates an expression and discards the result."""
|
||||||
|
fields = ('node',)
|
||||||
|
|
||||||
|
|
||||||
|
class Assign(Stmt):
|
||||||
|
"""Assigns an expression to a target."""
|
||||||
|
fields = ('target', 'node')
|
||||||
|
|
||||||
|
|
||||||
|
class Expr(Node):
|
||||||
|
"""Baseclass for all expressions."""
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
"""Return the value of the expression as constant or raise
|
||||||
|
:exc:`Impossible` if this was not possible.
|
||||||
|
|
||||||
|
An :class:`EvalContext` can be provided, if none is given
|
||||||
|
a default context is created which requires the nodes to have
|
||||||
|
an attached environment.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.4
|
||||||
|
the `eval_ctx` parameter was added.
|
||||||
|
"""
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
def can_assign(self):
|
||||||
|
"""Check if it's possible to assign something to this node."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class BinExpr(Expr):
|
||||||
|
"""Baseclass for all binary expressions."""
|
||||||
|
fields = ('left', 'right')
|
||||||
|
operator = None
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
# intercepted operators cannot be folded at compile time
|
||||||
|
if self.environment.sandboxed and \
|
||||||
|
self.operator in self.environment.intercepted_binops:
|
||||||
|
raise Impossible()
|
||||||
|
f = _binop_to_func[self.operator]
|
||||||
|
try:
|
||||||
|
return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
|
||||||
|
class UnaryExpr(Expr):
|
||||||
|
"""Baseclass for all unary expressions."""
|
||||||
|
fields = ('node',)
|
||||||
|
operator = None
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
# intercepted operators cannot be folded at compile time
|
||||||
|
if self.environment.sandboxed and \
|
||||||
|
self.operator in self.environment.intercepted_unops:
|
||||||
|
raise Impossible()
|
||||||
|
f = _uaop_to_func[self.operator]
|
||||||
|
try:
|
||||||
|
return f(self.node.as_const(eval_ctx))
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
|
||||||
|
class Name(Expr):
|
||||||
|
"""Looks up a name or stores a value in a name.
|
||||||
|
The `ctx` of the node can be one of the following values:
|
||||||
|
|
||||||
|
- `store`: store a value in the name
|
||||||
|
- `load`: load that name
|
||||||
|
- `param`: like `store` but if the name was defined as function parameter.
|
||||||
|
"""
|
||||||
|
fields = ('name', 'ctx')
|
||||||
|
|
||||||
|
def can_assign(self):
|
||||||
|
return self.name not in ('true', 'false', 'none',
|
||||||
|
'True', 'False', 'None')
|
||||||
|
|
||||||
|
|
||||||
|
class Literal(Expr):
|
||||||
|
"""Baseclass for literals."""
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class Const(Literal):
|
||||||
|
"""All constant values. The parser will return this node for simple
|
||||||
|
constants such as ``42`` or ``"foo"`` but it can be used to store more
|
||||||
|
complex values such as lists too. Only constants with a safe
|
||||||
|
representation (objects where ``eval(repr(x)) == x`` is true).
|
||||||
|
"""
|
||||||
|
fields = ('value',)
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_untrusted(cls, value, lineno=None, environment=None):
|
||||||
|
"""Return a const object if the value is representable as
|
||||||
|
constant value in the generated code, otherwise it will raise
|
||||||
|
an `Impossible` exception.
|
||||||
|
"""
|
||||||
|
from .compiler import has_safe_repr
|
||||||
|
if not has_safe_repr(value):
|
||||||
|
raise Impossible()
|
||||||
|
return cls(value, lineno=lineno, environment=environment)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateData(Literal):
|
||||||
|
"""A constant template string."""
|
||||||
|
fields = ('data',)
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
if eval_ctx.volatile:
|
||||||
|
raise Impossible()
|
||||||
|
if eval_ctx.autoescape:
|
||||||
|
return Markup(self.data)
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
|
||||||
|
class Tuple(Literal):
|
||||||
|
"""For loop unpacking and some other things like multiple arguments
|
||||||
|
for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
|
||||||
|
is used for loading the names or storing.
|
||||||
|
"""
|
||||||
|
fields = ('items', 'ctx')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return tuple(x.as_const(eval_ctx) for x in self.items)
|
||||||
|
|
||||||
|
def can_assign(self):
|
||||||
|
for item in self.items:
|
||||||
|
if not item.can_assign():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class List(Literal):
|
||||||
|
"""Any list literal such as ``[1, 2, 3]``"""
|
||||||
|
fields = ('items',)
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return [x.as_const(eval_ctx) for x in self.items]
|
||||||
|
|
||||||
|
|
||||||
|
class Dict(Literal):
|
||||||
|
"""Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
|
||||||
|
:class:`Pair` nodes.
|
||||||
|
"""
|
||||||
|
fields = ('items',)
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return dict(x.as_const(eval_ctx) for x in self.items)
|
||||||
|
|
||||||
|
|
||||||
|
class Pair(Helper):
|
||||||
|
"""A key, value pair for dicts."""
|
||||||
|
fields = ('key', 'value')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class Keyword(Helper):
|
||||||
|
"""A key, value pair for keyword arguments where key is a string."""
|
||||||
|
fields = ('key', 'value')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return self.key, self.value.as_const(eval_ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class CondExpr(Expr):
|
||||||
|
"""A conditional expression (inline if expression). (``{{
|
||||||
|
foo if bar else baz }}``)
|
||||||
|
"""
|
||||||
|
fields = ('test', 'expr1', 'expr2')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
if self.test.as_const(eval_ctx):
|
||||||
|
return self.expr1.as_const(eval_ctx)
|
||||||
|
|
||||||
|
# if we evaluate to an undefined object, we better do that at runtime
|
||||||
|
if self.expr2 is None:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
return self.expr2.as_const(eval_ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class Filter(Expr):
|
||||||
|
"""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`.
|
||||||
|
|
||||||
|
If the `node` of a filter is `None` the contents of the last buffer are
|
||||||
|
filtered. Buffers are created by macros and filter blocks.
|
||||||
|
"""
|
||||||
|
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
if eval_ctx.volatile or self.node is None:
|
||||||
|
raise Impossible()
|
||||||
|
# we have to be careful here because we call filter_ below.
|
||||||
|
# if this variable would be called filter, 2to3 would wrap 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
|
||||||
|
# python 3. because of that, do not rename filter_ to filter!
|
||||||
|
filter_ = self.environment.filters.get(self.name)
|
||||||
|
if filter_ is None or getattr(filter_, 'contextfilter', False):
|
||||||
|
raise Impossible()
|
||||||
|
obj = self.node.as_const(eval_ctx)
|
||||||
|
args = [x.as_const(eval_ctx) for x in self.args]
|
||||||
|
if getattr(filter_, 'evalcontextfilter', False):
|
||||||
|
args.insert(0, eval_ctx)
|
||||||
|
elif getattr(filter_, 'environmentfilter', False):
|
||||||
|
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:
|
||||||
|
return filter_(obj, *args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
|
||||||
|
class Test(Expr):
|
||||||
|
"""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`.
|
||||||
|
"""
|
||||||
|
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||||
|
|
||||||
|
|
||||||
|
class Call(Expr):
|
||||||
|
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
|
||||||
|
of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
|
||||||
|
and `dyn_kwargs` has to be either `None` or a node that is used as
|
||||||
|
node for dynamic positional (``*args``) or keyword (``**kwargs``)
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
if eval_ctx.volatile:
|
||||||
|
raise Impossible()
|
||||||
|
obj = self.node.as_const(eval_ctx)
|
||||||
|
|
||||||
|
# don't evaluate context functions
|
||||||
|
args = [x.as_const(eval_ctx) for x in self.args]
|
||||||
|
if isinstance(obj, _context_function_types):
|
||||||
|
if getattr(obj, 'contextfunction', False):
|
||||||
|
raise Impossible()
|
||||||
|
elif getattr(obj, 'evalcontextfunction', False):
|
||||||
|
args.insert(0, eval_ctx)
|
||||||
|
elif getattr(obj, 'environmentfunction', False):
|
||||||
|
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:
|
||||||
|
return obj(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
|
||||||
|
class Getitem(Expr):
|
||||||
|
"""Get an attribute or item from an expression and prefer the item."""
|
||||||
|
fields = ('node', 'arg', 'ctx')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
if self.ctx != 'load':
|
||||||
|
raise Impossible()
|
||||||
|
try:
|
||||||
|
return self.environment.getitem(self.node.as_const(eval_ctx),
|
||||||
|
self.arg.as_const(eval_ctx))
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
def can_assign(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Getattr(Expr):
|
||||||
|
"""Get an attribute or item from an expression that is a ascii-only
|
||||||
|
bytestring and prefer the attribute.
|
||||||
|
"""
|
||||||
|
fields = ('node', 'attr', 'ctx')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
if self.ctx != 'load':
|
||||||
|
raise Impossible()
|
||||||
|
try:
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return self.environment.getattr(self.node.as_const(eval_ctx),
|
||||||
|
self.attr)
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
|
||||||
|
def can_assign(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Slice(Expr):
|
||||||
|
"""Represents a slice object. This must only be used as argument for
|
||||||
|
:class:`Subscript`.
|
||||||
|
"""
|
||||||
|
fields = ('start', 'stop', 'step')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
def const(obj):
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
return obj.as_const(eval_ctx)
|
||||||
|
return slice(const(self.start), const(self.stop), const(self.step))
|
||||||
|
|
||||||
|
|
||||||
|
class Concat(Expr):
|
||||||
|
"""Concatenates the list of expressions provided after converting them to
|
||||||
|
unicode.
|
||||||
|
"""
|
||||||
|
fields = ('nodes',)
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes)
|
||||||
|
|
||||||
|
|
||||||
|
class Compare(Expr):
|
||||||
|
"""Compares an expression with some other expressions. `ops` must be a
|
||||||
|
list of :class:`Operand`\s.
|
||||||
|
"""
|
||||||
|
fields = ('expr', 'ops')
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
result = value = self.expr.as_const(eval_ctx)
|
||||||
|
try:
|
||||||
|
for op in self.ops:
|
||||||
|
new_value = op.expr.as_const(eval_ctx)
|
||||||
|
result = _cmpop_to_func[op.op](value, new_value)
|
||||||
|
value = new_value
|
||||||
|
except Exception:
|
||||||
|
raise Impossible()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class Operand(Helper):
|
||||||
|
"""Holds an operator and an expression."""
|
||||||
|
fields = ('op', 'expr')
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
Operand.__doc__ += '\nThe following operators are available: ' + \
|
||||||
|
', '.join(sorted('``%s``' % x for x in set(_binop_to_func) |
|
||||||
|
set(_uaop_to_func) | set(_cmpop_to_func)))
|
||||||
|
|
||||||
|
|
||||||
|
class Mul(BinExpr):
|
||||||
|
"""Multiplies the left with the right node."""
|
||||||
|
operator = '*'
|
||||||
|
|
||||||
|
|
||||||
|
class Div(BinExpr):
|
||||||
|
"""Divides the left by the right node."""
|
||||||
|
operator = '/'
|
||||||
|
|
||||||
|
|
||||||
|
class FloorDiv(BinExpr):
|
||||||
|
"""Divides the left by the right node and truncates conver the
|
||||||
|
result into an integer by truncating.
|
||||||
|
"""
|
||||||
|
operator = '//'
|
||||||
|
|
||||||
|
|
||||||
|
class Add(BinExpr):
|
||||||
|
"""Add the left to the right node."""
|
||||||
|
operator = '+'
|
||||||
|
|
||||||
|
|
||||||
|
class Sub(BinExpr):
|
||||||
|
"""Substract the right from the left node."""
|
||||||
|
operator = '-'
|
||||||
|
|
||||||
|
|
||||||
|
class Mod(BinExpr):
|
||||||
|
"""Left modulo right."""
|
||||||
|
operator = '%'
|
||||||
|
|
||||||
|
|
||||||
|
class Pow(BinExpr):
|
||||||
|
"""Left to the power of right."""
|
||||||
|
operator = '**'
|
||||||
|
|
||||||
|
|
||||||
|
class And(BinExpr):
|
||||||
|
"""Short circuited AND."""
|
||||||
|
operator = 'and'
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class Or(BinExpr):
|
||||||
|
"""Short circuited OR."""
|
||||||
|
operator = 'or'
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class Not(UnaryExpr):
|
||||||
|
"""Negate the expression."""
|
||||||
|
operator = 'not'
|
||||||
|
|
||||||
|
|
||||||
|
class Neg(UnaryExpr):
|
||||||
|
"""Make the expression negative."""
|
||||||
|
operator = '-'
|
||||||
|
|
||||||
|
|
||||||
|
class Pos(UnaryExpr):
|
||||||
|
"""Make the expression positive (noop for most expressions)"""
|
||||||
|
operator = '+'
|
||||||
|
|
||||||
|
|
||||||
|
# Helpers for extensions
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironmentAttribute(Expr):
|
||||||
|
"""Loads an attribute from the environment object. This is useful for
|
||||||
|
extensions that want to call a callback stored on the environment.
|
||||||
|
"""
|
||||||
|
fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionAttribute(Expr):
|
||||||
|
"""Returns the attribute of an extension bound to the environment.
|
||||||
|
The identifier is the identifier of the :class:`Extension`.
|
||||||
|
|
||||||
|
This node is usually constructed by calling the
|
||||||
|
:meth:`~jinja2.ext.Extension.attr` method on an extension.
|
||||||
|
"""
|
||||||
|
fields = ('identifier', 'name')
|
||||||
|
|
||||||
|
|
||||||
|
class ImportedName(Expr):
|
||||||
|
"""If created with an import name the import name is returned on node
|
||||||
|
access. For example ``ImportedName('cgi.escape')`` returns the `escape`
|
||||||
|
function from the cgi module on evaluation. Imports are optimized by the
|
||||||
|
compiler so there is no need to assign them to local variables.
|
||||||
|
"""
|
||||||
|
fields = ('importname',)
|
||||||
|
|
||||||
|
|
||||||
|
class InternalName(Expr):
|
||||||
|
"""An internal name in the compiler. You cannot create these nodes
|
||||||
|
yourself but the parser provides a
|
||||||
|
:meth:`~jinja2.parser.Parser.free_identifier` method that creates
|
||||||
|
a new identifier for you. This identifier is not available from the
|
||||||
|
template and is not threated specially by the compiler.
|
||||||
|
"""
|
||||||
|
fields = ('name',)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
raise TypeError('Can\'t create internal names. Use the '
|
||||||
|
'`free_identifier` method on a parser.')
|
||||||
|
|
||||||
|
|
||||||
|
class MarkSafe(Expr):
|
||||||
|
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
|
||||||
|
fields = ('expr',)
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
return Markup(self.expr.as_const(eval_ctx))
|
||||||
|
|
||||||
|
|
||||||
|
class MarkSafeIfAutoescape(Expr):
|
||||||
|
"""Mark the wrapped expression as safe (wrap it as `Markup`) but
|
||||||
|
only if autoescaping is active.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
"""
|
||||||
|
fields = ('expr',)
|
||||||
|
|
||||||
|
def as_const(self, eval_ctx=None):
|
||||||
|
eval_ctx = get_eval_context(self, eval_ctx)
|
||||||
|
if eval_ctx.volatile:
|
||||||
|
raise Impossible()
|
||||||
|
expr = self.expr.as_const(eval_ctx)
|
||||||
|
if eval_ctx.autoescape:
|
||||||
|
return Markup(expr)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
|
||||||
|
class ContextReference(Expr):
|
||||||
|
"""Returns the current template context. It can be used like a
|
||||||
|
:class:`Name` node, with a ``'load'`` ctx and will return the
|
||||||
|
current :class:`~jinja2.runtime.Context` object.
|
||||||
|
|
||||||
|
Here an example that assigns the current template name to a
|
||||||
|
variable named `foo`::
|
||||||
|
|
||||||
|
Assign(Name('foo', ctx='store'),
|
||||||
|
Getattr(ContextReference(), 'name'))
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Continue(Stmt):
|
||||||
|
"""Continue a loop."""
|
||||||
|
|
||||||
|
|
||||||
|
class Break(Stmt):
|
||||||
|
"""Break a loop."""
|
||||||
|
|
||||||
|
|
||||||
|
class Scope(Stmt):
|
||||||
|
"""An artificial scope."""
|
||||||
|
fields = ('body',)
|
||||||
|
|
||||||
|
|
||||||
|
class EvalContextModifier(Stmt):
|
||||||
|
"""Modifies the eval context. For each option that should be modified,
|
||||||
|
a :class:`Keyword` has to be added to the :attr:`options` list.
|
||||||
|
|
||||||
|
Example to change the `autoescape` setting::
|
||||||
|
|
||||||
|
EvalContextModifier(options=[Keyword('autoescape', Const(True))])
|
||||||
|
"""
|
||||||
|
fields = ('options',)
|
||||||
|
|
||||||
|
|
||||||
|
class ScopedEvalContextModifier(EvalContextModifier):
|
||||||
|
"""Modifies the eval context and reverts it later. Works exactly like
|
||||||
|
:class:`EvalContextModifier` but will only modify the
|
||||||
|
:class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
|
||||||
|
"""
|
||||||
|
fields = ('body',)
|
||||||
|
|
||||||
|
|
||||||
|
# make sure nobody creates custom nodes
|
||||||
|
def _failing_new(*args, **kwargs):
|
||||||
|
raise TypeError('can\'t create custom node types')
|
||||||
|
NodeType.__new__ = staticmethod(_failing_new); del _failing_new
|
68
tools/external/jinja2/optimizer.py
vendored
Normal file
68
tools/external/jinja2/optimizer.py
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.optimizer
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The jinja optimizer is currently trying to constant fold a few expressions
|
||||||
|
and modify the AST in place so that it should be easier to evaluate it.
|
||||||
|
|
||||||
|
Because the AST does not contain all the scoping information and the
|
||||||
|
compiler has to find that out, we cannot do all the optimizations we
|
||||||
|
want. For example loop unrolling doesn't work because unrolled loops would
|
||||||
|
have a different scoping.
|
||||||
|
|
||||||
|
The solution would be a second syntax tree that has the scoping rules stored.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD.
|
||||||
|
"""
|
||||||
|
from jinja2 import nodes
|
||||||
|
from jinja2.visitor import NodeTransformer
|
||||||
|
|
||||||
|
|
||||||
|
def optimize(node, environment):
|
||||||
|
"""The context hint can be used to perform an static optimization
|
||||||
|
based on the context given."""
|
||||||
|
optimizer = Optimizer(environment)
|
||||||
|
return optimizer.visit(node)
|
||||||
|
|
||||||
|
|
||||||
|
class Optimizer(NodeTransformer):
|
||||||
|
|
||||||
|
def __init__(self, environment):
|
||||||
|
self.environment = environment
|
||||||
|
|
||||||
|
def visit_If(self, node):
|
||||||
|
"""Eliminate dead code."""
|
||||||
|
# do not optimize ifs that have a block inside so that it doesn't
|
||||||
|
# break super().
|
||||||
|
if node.find(nodes.Block) is not None:
|
||||||
|
return self.generic_visit(node)
|
||||||
|
try:
|
||||||
|
val = self.visit(node.test).as_const()
|
||||||
|
except nodes.Impossible:
|
||||||
|
return self.generic_visit(node)
|
||||||
|
if val:
|
||||||
|
body = node.body
|
||||||
|
else:
|
||||||
|
body = node.else_
|
||||||
|
result = []
|
||||||
|
for node in body:
|
||||||
|
result.extend(self.visit_list(node))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def fold(self, node):
|
||||||
|
"""Do constant folding."""
|
||||||
|
node = self.generic_visit(node)
|
||||||
|
try:
|
||||||
|
return nodes.Const.from_untrusted(node.as_const(),
|
||||||
|
lineno=node.lineno,
|
||||||
|
environment=self.environment)
|
||||||
|
except nodes.Impossible:
|
||||||
|
return node
|
||||||
|
|
||||||
|
visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
|
||||||
|
visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
|
||||||
|
visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \
|
||||||
|
visit_Filter = visit_Test = visit_CondExpr = fold
|
||||||
|
del fold
|
895
tools/external/jinja2/parser.py
vendored
Normal file
895
tools/external/jinja2/parser.py
vendored
Normal file
|
@ -0,0 +1,895 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.parser
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the template parser.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
from jinja2 import nodes
|
||||||
|
from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
|
||||||
|
from jinja2.lexer import describe_token, describe_token_expr
|
||||||
|
from jinja2._compat import next, imap
|
||||||
|
|
||||||
|
|
||||||
|
#: statements that callinto
|
||||||
|
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
|
||||||
|
'macro', 'include', 'from', 'import',
|
||||||
|
'set'])
|
||||||
|
_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
|
||||||
|
|
||||||
|
|
||||||
|
class Parser(object):
|
||||||
|
"""This is the central parsing class Jinja2 uses. It's passed to
|
||||||
|
extensions and can be used to parse expressions or statements.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, environment, source, name=None, filename=None,
|
||||||
|
state=None):
|
||||||
|
self.environment = environment
|
||||||
|
self.stream = environment._tokenize(source, name, filename, state)
|
||||||
|
self.name = name
|
||||||
|
self.filename = filename
|
||||||
|
self.closed = False
|
||||||
|
self.extensions = {}
|
||||||
|
for extension in environment.iter_extensions():
|
||||||
|
for tag in extension.tags:
|
||||||
|
self.extensions[tag] = extension.parse
|
||||||
|
self._last_identifier = 0
|
||||||
|
self._tag_stack = []
|
||||||
|
self._end_token_stack = []
|
||||||
|
|
||||||
|
def fail(self, msg, lineno=None, exc=TemplateSyntaxError):
|
||||||
|
"""Convenience method that raises `exc` with the message, passed
|
||||||
|
line number or last line number as well as the current name and
|
||||||
|
filename.
|
||||||
|
"""
|
||||||
|
if lineno is None:
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
raise exc(msg, lineno, self.name, self.filename)
|
||||||
|
|
||||||
|
def _fail_ut_eof(self, name, end_token_stack, lineno):
|
||||||
|
expected = []
|
||||||
|
for exprs in end_token_stack:
|
||||||
|
expected.extend(imap(describe_token_expr, exprs))
|
||||||
|
if end_token_stack:
|
||||||
|
currently_looking = ' or '.join(
|
||||||
|
"'%s'" % describe_token_expr(expr)
|
||||||
|
for expr in end_token_stack[-1])
|
||||||
|
else:
|
||||||
|
currently_looking = None
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
message = ['Unexpected end of template.']
|
||||||
|
else:
|
||||||
|
message = ['Encountered unknown tag \'%s\'.' % name]
|
||||||
|
|
||||||
|
if currently_looking:
|
||||||
|
if name is not None and name in expected:
|
||||||
|
message.append('You probably made a nesting mistake. Jinja '
|
||||||
|
'is expecting this tag, but currently looking '
|
||||||
|
'for %s.' % currently_looking)
|
||||||
|
else:
|
||||||
|
message.append('Jinja was looking for the following tags: '
|
||||||
|
'%s.' % currently_looking)
|
||||||
|
|
||||||
|
if self._tag_stack:
|
||||||
|
message.append('The innermost block that needs to be '
|
||||||
|
'closed is \'%s\'.' % self._tag_stack[-1])
|
||||||
|
|
||||||
|
self.fail(' '.join(message), lineno)
|
||||||
|
|
||||||
|
def fail_unknown_tag(self, name, lineno=None):
|
||||||
|
"""Called if the parser encounters an unknown tag. Tries to fail
|
||||||
|
with a human readable error message that could help to identify
|
||||||
|
the problem.
|
||||||
|
"""
|
||||||
|
return self._fail_ut_eof(name, self._end_token_stack, lineno)
|
||||||
|
|
||||||
|
def fail_eof(self, end_tokens=None, lineno=None):
|
||||||
|
"""Like fail_unknown_tag but for end of template situations."""
|
||||||
|
stack = list(self._end_token_stack)
|
||||||
|
if end_tokens is not None:
|
||||||
|
stack.append(end_tokens)
|
||||||
|
return self._fail_ut_eof(None, stack, lineno)
|
||||||
|
|
||||||
|
def is_tuple_end(self, extra_end_rules=None):
|
||||||
|
"""Are we at the end of a tuple?"""
|
||||||
|
if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
|
||||||
|
return True
|
||||||
|
elif extra_end_rules is not None:
|
||||||
|
return self.stream.current.test_any(extra_end_rules)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def free_identifier(self, lineno=None):
|
||||||
|
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
|
||||||
|
self._last_identifier += 1
|
||||||
|
rv = object.__new__(nodes.InternalName)
|
||||||
|
nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def parse_statement(self):
|
||||||
|
"""Parse a single statement."""
|
||||||
|
token = self.stream.current
|
||||||
|
if token.type != 'name':
|
||||||
|
self.fail('tag name expected', token.lineno)
|
||||||
|
self._tag_stack.append(token.value)
|
||||||
|
pop_tag = True
|
||||||
|
try:
|
||||||
|
if token.value in _statement_keywords:
|
||||||
|
return getattr(self, 'parse_' + self.stream.current.value)()
|
||||||
|
if token.value == 'call':
|
||||||
|
return self.parse_call_block()
|
||||||
|
if token.value == 'filter':
|
||||||
|
return self.parse_filter_block()
|
||||||
|
ext = self.extensions.get(token.value)
|
||||||
|
if ext is not None:
|
||||||
|
return ext(self)
|
||||||
|
|
||||||
|
# did not work out, remove the token we pushed by accident
|
||||||
|
# from the stack so that the unknown tag fail function can
|
||||||
|
# produce a proper error message.
|
||||||
|
self._tag_stack.pop()
|
||||||
|
pop_tag = False
|
||||||
|
self.fail_unknown_tag(token.value, token.lineno)
|
||||||
|
finally:
|
||||||
|
if pop_tag:
|
||||||
|
self._tag_stack.pop()
|
||||||
|
|
||||||
|
def parse_statements(self, end_tokens, drop_needle=False):
|
||||||
|
"""Parse multiple statements into a list until one of the end tokens
|
||||||
|
is reached. This is used to parse the body of statements as it also
|
||||||
|
parses template data if appropriate. The parser checks first if the
|
||||||
|
current token is a colon and skips it if there is one. Then it checks
|
||||||
|
for the block end and parses until if one of the `end_tokens` is
|
||||||
|
reached. Per default the active token in the stream at the end of
|
||||||
|
the call is the matched end token. If this is not wanted `drop_needle`
|
||||||
|
can be set to `True` and the end token is removed.
|
||||||
|
"""
|
||||||
|
# the first token may be a colon for python compatibility
|
||||||
|
self.stream.skip_if('colon')
|
||||||
|
|
||||||
|
# in the future it would be possible to add whole code sections
|
||||||
|
# by adding some sort of end of statement token and parsing those here.
|
||||||
|
self.stream.expect('block_end')
|
||||||
|
result = self.subparse(end_tokens)
|
||||||
|
|
||||||
|
# we reached the end of the template too early, the subparser
|
||||||
|
# does not check for this, so we do that now
|
||||||
|
if self.stream.current.type == 'eof':
|
||||||
|
self.fail_eof(end_tokens)
|
||||||
|
|
||||||
|
if drop_needle:
|
||||||
|
next(self.stream)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def parse_set(self):
|
||||||
|
"""Parse an assign statement."""
|
||||||
|
lineno = next(self.stream).lineno
|
||||||
|
target = self.parse_assign_target()
|
||||||
|
self.stream.expect('assign')
|
||||||
|
expr = self.parse_tuple()
|
||||||
|
return nodes.Assign(target, expr, lineno=lineno)
|
||||||
|
|
||||||
|
def parse_for(self):
|
||||||
|
"""Parse a for loop."""
|
||||||
|
lineno = self.stream.expect('name:for').lineno
|
||||||
|
target = self.parse_assign_target(extra_end_rules=('name:in',))
|
||||||
|
self.stream.expect('name:in')
|
||||||
|
iter = self.parse_tuple(with_condexpr=False,
|
||||||
|
extra_end_rules=('name:recursive',))
|
||||||
|
test = None
|
||||||
|
if self.stream.skip_if('name:if'):
|
||||||
|
test = self.parse_expression()
|
||||||
|
recursive = self.stream.skip_if('name:recursive')
|
||||||
|
body = self.parse_statements(('name:endfor', 'name:else'))
|
||||||
|
if next(self.stream).value == 'endfor':
|
||||||
|
else_ = []
|
||||||
|
else:
|
||||||
|
else_ = self.parse_statements(('name:endfor',), drop_needle=True)
|
||||||
|
return nodes.For(target, iter, body, else_, test,
|
||||||
|
recursive, lineno=lineno)
|
||||||
|
|
||||||
|
def parse_if(self):
|
||||||
|
"""Parse an if construct."""
|
||||||
|
node = result = nodes.If(lineno=self.stream.expect('name:if').lineno)
|
||||||
|
while 1:
|
||||||
|
node.test = self.parse_tuple(with_condexpr=False)
|
||||||
|
node.body = self.parse_statements(('name:elif', 'name:else',
|
||||||
|
'name:endif'))
|
||||||
|
token = next(self.stream)
|
||||||
|
if token.test('name:elif'):
|
||||||
|
new_node = nodes.If(lineno=self.stream.current.lineno)
|
||||||
|
node.else_ = [new_node]
|
||||||
|
node = new_node
|
||||||
|
continue
|
||||||
|
elif token.test('name:else'):
|
||||||
|
node.else_ = self.parse_statements(('name:endif',),
|
||||||
|
drop_needle=True)
|
||||||
|
else:
|
||||||
|
node.else_ = []
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
def parse_block(self):
|
||||||
|
node = nodes.Block(lineno=next(self.stream).lineno)
|
||||||
|
node.name = self.stream.expect('name').value
|
||||||
|
node.scoped = self.stream.skip_if('name:scoped')
|
||||||
|
|
||||||
|
# common problem people encounter when switching from django
|
||||||
|
# to jinja. we do not support hyphens in block names, so let's
|
||||||
|
# raise a nicer error message in that case.
|
||||||
|
if self.stream.current.type == 'sub':
|
||||||
|
self.fail('Block names in Jinja have to be valid Python '
|
||||||
|
'identifiers and may not contain hyphens, use an '
|
||||||
|
'underscore instead.')
|
||||||
|
|
||||||
|
node.body = self.parse_statements(('name:endblock',), drop_needle=True)
|
||||||
|
self.stream.skip_if('name:' + node.name)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_extends(self):
|
||||||
|
node = nodes.Extends(lineno=next(self.stream).lineno)
|
||||||
|
node.template = self.parse_expression()
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_import_context(self, node, default):
|
||||||
|
if self.stream.current.test_any('name:with', 'name:without') and \
|
||||||
|
self.stream.look().test('name:context'):
|
||||||
|
node.with_context = next(self.stream).value == 'with'
|
||||||
|
self.stream.skip()
|
||||||
|
else:
|
||||||
|
node.with_context = default
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_include(self):
|
||||||
|
node = nodes.Include(lineno=next(self.stream).lineno)
|
||||||
|
node.template = self.parse_expression()
|
||||||
|
if self.stream.current.test('name:ignore') and \
|
||||||
|
self.stream.look().test('name:missing'):
|
||||||
|
node.ignore_missing = True
|
||||||
|
self.stream.skip(2)
|
||||||
|
else:
|
||||||
|
node.ignore_missing = False
|
||||||
|
return self.parse_import_context(node, True)
|
||||||
|
|
||||||
|
def parse_import(self):
|
||||||
|
node = nodes.Import(lineno=next(self.stream).lineno)
|
||||||
|
node.template = self.parse_expression()
|
||||||
|
self.stream.expect('name:as')
|
||||||
|
node.target = self.parse_assign_target(name_only=True).name
|
||||||
|
return self.parse_import_context(node, False)
|
||||||
|
|
||||||
|
def parse_from(self):
|
||||||
|
node = nodes.FromImport(lineno=next(self.stream).lineno)
|
||||||
|
node.template = self.parse_expression()
|
||||||
|
self.stream.expect('name:import')
|
||||||
|
node.names = []
|
||||||
|
|
||||||
|
def parse_context():
|
||||||
|
if self.stream.current.value in ('with', 'without') and \
|
||||||
|
self.stream.look().test('name:context'):
|
||||||
|
node.with_context = next(self.stream).value == 'with'
|
||||||
|
self.stream.skip()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
if node.names:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
if self.stream.current.type == 'name':
|
||||||
|
if parse_context():
|
||||||
|
break
|
||||||
|
target = self.parse_assign_target(name_only=True)
|
||||||
|
if target.name.startswith('_'):
|
||||||
|
self.fail('names starting with an underline can not '
|
||||||
|
'be imported', target.lineno,
|
||||||
|
exc=TemplateAssertionError)
|
||||||
|
if self.stream.skip_if('name:as'):
|
||||||
|
alias = self.parse_assign_target(name_only=True)
|
||||||
|
node.names.append((target.name, alias.name))
|
||||||
|
else:
|
||||||
|
node.names.append(target.name)
|
||||||
|
if parse_context() or self.stream.current.type != 'comma':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if not hasattr(node, 'with_context'):
|
||||||
|
node.with_context = False
|
||||||
|
self.stream.skip_if('comma')
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_signature(self, node):
|
||||||
|
node.args = args = []
|
||||||
|
node.defaults = defaults = []
|
||||||
|
self.stream.expect('lparen')
|
||||||
|
while self.stream.current.type != 'rparen':
|
||||||
|
if args:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
arg = self.parse_assign_target(name_only=True)
|
||||||
|
arg.set_ctx('param')
|
||||||
|
if self.stream.skip_if('assign'):
|
||||||
|
defaults.append(self.parse_expression())
|
||||||
|
args.append(arg)
|
||||||
|
self.stream.expect('rparen')
|
||||||
|
|
||||||
|
def parse_call_block(self):
|
||||||
|
node = nodes.CallBlock(lineno=next(self.stream).lineno)
|
||||||
|
if self.stream.current.type == 'lparen':
|
||||||
|
self.parse_signature(node)
|
||||||
|
else:
|
||||||
|
node.args = []
|
||||||
|
node.defaults = []
|
||||||
|
|
||||||
|
node.call = self.parse_expression()
|
||||||
|
if not isinstance(node.call, nodes.Call):
|
||||||
|
self.fail('expected call', node.lineno)
|
||||||
|
node.body = self.parse_statements(('name:endcall',), drop_needle=True)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_filter_block(self):
|
||||||
|
node = nodes.FilterBlock(lineno=next(self.stream).lineno)
|
||||||
|
node.filter = self.parse_filter(None, start_inline=True)
|
||||||
|
node.body = self.parse_statements(('name:endfilter',),
|
||||||
|
drop_needle=True)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_macro(self):
|
||||||
|
node = nodes.Macro(lineno=next(self.stream).lineno)
|
||||||
|
node.name = self.parse_assign_target(name_only=True).name
|
||||||
|
self.parse_signature(node)
|
||||||
|
node.body = self.parse_statements(('name:endmacro',),
|
||||||
|
drop_needle=True)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_print(self):
|
||||||
|
node = nodes.Output(lineno=next(self.stream).lineno)
|
||||||
|
node.nodes = []
|
||||||
|
while self.stream.current.type != 'block_end':
|
||||||
|
if node.nodes:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
node.nodes.append(self.parse_expression())
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_assign_target(self, with_tuple=True, name_only=False,
|
||||||
|
extra_end_rules=None):
|
||||||
|
"""Parse an assignment target. As Jinja2 allows assignments to
|
||||||
|
tuples, this function can parse all allowed assignment targets. Per
|
||||||
|
default assignments to tuples are parsed, that can be disable however
|
||||||
|
by setting `with_tuple` to `False`. If only assignments to names are
|
||||||
|
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
||||||
|
parameter is forwarded to the tuple parsing function.
|
||||||
|
"""
|
||||||
|
if name_only:
|
||||||
|
token = self.stream.expect('name')
|
||||||
|
target = nodes.Name(token.value, 'store', lineno=token.lineno)
|
||||||
|
else:
|
||||||
|
if with_tuple:
|
||||||
|
target = self.parse_tuple(simplified=True,
|
||||||
|
extra_end_rules=extra_end_rules)
|
||||||
|
else:
|
||||||
|
target = self.parse_primary()
|
||||||
|
target.set_ctx('store')
|
||||||
|
if not target.can_assign():
|
||||||
|
self.fail('can\'t assign to %r' % target.__class__.
|
||||||
|
__name__.lower(), target.lineno)
|
||||||
|
return target
|
||||||
|
|
||||||
|
def parse_expression(self, with_condexpr=True):
|
||||||
|
"""Parse an expression. Per default all expressions are parsed, if
|
||||||
|
the optional `with_condexpr` parameter is set to `False` conditional
|
||||||
|
expressions are not parsed.
|
||||||
|
"""
|
||||||
|
if with_condexpr:
|
||||||
|
return self.parse_condexpr()
|
||||||
|
return self.parse_or()
|
||||||
|
|
||||||
|
def parse_condexpr(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
expr1 = self.parse_or()
|
||||||
|
while self.stream.skip_if('name:if'):
|
||||||
|
expr2 = self.parse_or()
|
||||||
|
if self.stream.skip_if('name:else'):
|
||||||
|
expr3 = self.parse_condexpr()
|
||||||
|
else:
|
||||||
|
expr3 = None
|
||||||
|
expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return expr1
|
||||||
|
|
||||||
|
def parse_or(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_and()
|
||||||
|
while self.stream.skip_if('name:or'):
|
||||||
|
right = self.parse_and()
|
||||||
|
left = nodes.Or(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_and(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_not()
|
||||||
|
while self.stream.skip_if('name:and'):
|
||||||
|
right = self.parse_not()
|
||||||
|
left = nodes.And(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_not(self):
|
||||||
|
if self.stream.current.test('name:not'):
|
||||||
|
lineno = next(self.stream).lineno
|
||||||
|
return nodes.Not(self.parse_not(), lineno=lineno)
|
||||||
|
return self.parse_compare()
|
||||||
|
|
||||||
|
def parse_compare(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
expr = self.parse_add()
|
||||||
|
ops = []
|
||||||
|
while 1:
|
||||||
|
token_type = self.stream.current.type
|
||||||
|
if token_type in _compare_operators:
|
||||||
|
next(self.stream)
|
||||||
|
ops.append(nodes.Operand(token_type, self.parse_add()))
|
||||||
|
elif self.stream.skip_if('name:in'):
|
||||||
|
ops.append(nodes.Operand('in', self.parse_add()))
|
||||||
|
elif self.stream.current.test('name:not') and \
|
||||||
|
self.stream.look().test('name:in'):
|
||||||
|
self.stream.skip(2)
|
||||||
|
ops.append(nodes.Operand('notin', self.parse_add()))
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
if not ops:
|
||||||
|
return expr
|
||||||
|
return nodes.Compare(expr, ops, lineno=lineno)
|
||||||
|
|
||||||
|
def parse_add(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_sub()
|
||||||
|
while self.stream.current.type == 'add':
|
||||||
|
next(self.stream)
|
||||||
|
right = self.parse_sub()
|
||||||
|
left = nodes.Add(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_sub(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_concat()
|
||||||
|
while self.stream.current.type == 'sub':
|
||||||
|
next(self.stream)
|
||||||
|
right = self.parse_concat()
|
||||||
|
left = nodes.Sub(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_concat(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
args = [self.parse_mul()]
|
||||||
|
while self.stream.current.type == 'tilde':
|
||||||
|
next(self.stream)
|
||||||
|
args.append(self.parse_mul())
|
||||||
|
if len(args) == 1:
|
||||||
|
return args[0]
|
||||||
|
return nodes.Concat(args, lineno=lineno)
|
||||||
|
|
||||||
|
def parse_mul(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_div()
|
||||||
|
while self.stream.current.type == 'mul':
|
||||||
|
next(self.stream)
|
||||||
|
right = self.parse_div()
|
||||||
|
left = nodes.Mul(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_div(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_floordiv()
|
||||||
|
while self.stream.current.type == 'div':
|
||||||
|
next(self.stream)
|
||||||
|
right = self.parse_floordiv()
|
||||||
|
left = nodes.Div(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_floordiv(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_mod()
|
||||||
|
while self.stream.current.type == 'floordiv':
|
||||||
|
next(self.stream)
|
||||||
|
right = self.parse_mod()
|
||||||
|
left = nodes.FloorDiv(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_mod(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_pow()
|
||||||
|
while self.stream.current.type == 'mod':
|
||||||
|
next(self.stream)
|
||||||
|
right = self.parse_pow()
|
||||||
|
left = nodes.Mod(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_pow(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
left = self.parse_unary()
|
||||||
|
while self.stream.current.type == 'pow':
|
||||||
|
next(self.stream)
|
||||||
|
right = self.parse_unary()
|
||||||
|
left = nodes.Pow(left, right, lineno=lineno)
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
return left
|
||||||
|
|
||||||
|
def parse_unary(self, with_filter=True):
|
||||||
|
token_type = self.stream.current.type
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
if token_type == 'sub':
|
||||||
|
next(self.stream)
|
||||||
|
node = nodes.Neg(self.parse_unary(False), lineno=lineno)
|
||||||
|
elif token_type == 'add':
|
||||||
|
next(self.stream)
|
||||||
|
node = nodes.Pos(self.parse_unary(False), lineno=lineno)
|
||||||
|
else:
|
||||||
|
node = self.parse_primary()
|
||||||
|
node = self.parse_postfix(node)
|
||||||
|
if with_filter:
|
||||||
|
node = self.parse_filter_expr(node)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_primary(self):
|
||||||
|
token = self.stream.current
|
||||||
|
if token.type == 'name':
|
||||||
|
if token.value in ('true', 'false', 'True', 'False'):
|
||||||
|
node = nodes.Const(token.value in ('true', 'True'),
|
||||||
|
lineno=token.lineno)
|
||||||
|
elif token.value in ('none', 'None'):
|
||||||
|
node = nodes.Const(None, lineno=token.lineno)
|
||||||
|
else:
|
||||||
|
node = nodes.Name(token.value, 'load', lineno=token.lineno)
|
||||||
|
next(self.stream)
|
||||||
|
elif token.type == 'string':
|
||||||
|
next(self.stream)
|
||||||
|
buf = [token.value]
|
||||||
|
lineno = token.lineno
|
||||||
|
while self.stream.current.type == 'string':
|
||||||
|
buf.append(self.stream.current.value)
|
||||||
|
next(self.stream)
|
||||||
|
node = nodes.Const(''.join(buf), lineno=lineno)
|
||||||
|
elif token.type in ('integer', 'float'):
|
||||||
|
next(self.stream)
|
||||||
|
node = nodes.Const(token.value, lineno=token.lineno)
|
||||||
|
elif token.type == 'lparen':
|
||||||
|
next(self.stream)
|
||||||
|
node = self.parse_tuple(explicit_parentheses=True)
|
||||||
|
self.stream.expect('rparen')
|
||||||
|
elif token.type == 'lbracket':
|
||||||
|
node = self.parse_list()
|
||||||
|
elif token.type == 'lbrace':
|
||||||
|
node = self.parse_dict()
|
||||||
|
else:
|
||||||
|
self.fail("unexpected '%s'" % describe_token(token), token.lineno)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_tuple(self, simplified=False, with_condexpr=True,
|
||||||
|
extra_end_rules=None, explicit_parentheses=False):
|
||||||
|
"""Works like `parse_expression` but if multiple expressions are
|
||||||
|
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
|
||||||
|
This method could also return a regular expression instead of a tuple
|
||||||
|
if no commas where found.
|
||||||
|
|
||||||
|
The default parsing mode is a full tuple. If `simplified` is `True`
|
||||||
|
only names and literals are parsed. The `no_condexpr` parameter is
|
||||||
|
forwarded to :meth:`parse_expression`.
|
||||||
|
|
||||||
|
Because tuples do not require delimiters and may end in a bogus comma
|
||||||
|
an extra hint is needed that marks the end of a tuple. For example
|
||||||
|
for loops support tuples between `for` and `in`. In that case the
|
||||||
|
`extra_end_rules` is set to ``['name:in']``.
|
||||||
|
|
||||||
|
`explicit_parentheses` is true if the parsing was triggered by an
|
||||||
|
expression in parentheses. This is used to figure out if an empty
|
||||||
|
tuple is a valid expression or not.
|
||||||
|
"""
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
if simplified:
|
||||||
|
parse = self.parse_primary
|
||||||
|
elif with_condexpr:
|
||||||
|
parse = self.parse_expression
|
||||||
|
else:
|
||||||
|
parse = lambda: self.parse_expression(with_condexpr=False)
|
||||||
|
args = []
|
||||||
|
is_tuple = False
|
||||||
|
while 1:
|
||||||
|
if args:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
if self.is_tuple_end(extra_end_rules):
|
||||||
|
break
|
||||||
|
args.append(parse())
|
||||||
|
if self.stream.current.type == 'comma':
|
||||||
|
is_tuple = True
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
|
||||||
|
if not is_tuple:
|
||||||
|
if args:
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
# if we don't have explicit parentheses, an empty tuple is
|
||||||
|
# not a valid expression. This would mean nothing (literally
|
||||||
|
# nothing) in the spot of an expression would be an empty
|
||||||
|
# tuple.
|
||||||
|
if not explicit_parentheses:
|
||||||
|
self.fail('Expected an expression, got \'%s\'' %
|
||||||
|
describe_token(self.stream.current))
|
||||||
|
|
||||||
|
return nodes.Tuple(args, 'load', lineno=lineno)
|
||||||
|
|
||||||
|
def parse_list(self):
|
||||||
|
token = self.stream.expect('lbracket')
|
||||||
|
items = []
|
||||||
|
while self.stream.current.type != 'rbracket':
|
||||||
|
if items:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
if self.stream.current.type == 'rbracket':
|
||||||
|
break
|
||||||
|
items.append(self.parse_expression())
|
||||||
|
self.stream.expect('rbracket')
|
||||||
|
return nodes.List(items, lineno=token.lineno)
|
||||||
|
|
||||||
|
def parse_dict(self):
|
||||||
|
token = self.stream.expect('lbrace')
|
||||||
|
items = []
|
||||||
|
while self.stream.current.type != 'rbrace':
|
||||||
|
if items:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
if self.stream.current.type == 'rbrace':
|
||||||
|
break
|
||||||
|
key = self.parse_expression()
|
||||||
|
self.stream.expect('colon')
|
||||||
|
value = self.parse_expression()
|
||||||
|
items.append(nodes.Pair(key, value, lineno=key.lineno))
|
||||||
|
self.stream.expect('rbrace')
|
||||||
|
return nodes.Dict(items, lineno=token.lineno)
|
||||||
|
|
||||||
|
def parse_postfix(self, node):
|
||||||
|
while 1:
|
||||||
|
token_type = self.stream.current.type
|
||||||
|
if token_type == 'dot' or token_type == 'lbracket':
|
||||||
|
node = self.parse_subscript(node)
|
||||||
|
# calls are valid both after postfix expressions (getattr
|
||||||
|
# and getitem) as well as filters and tests
|
||||||
|
elif token_type == 'lparen':
|
||||||
|
node = self.parse_call(node)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_filter_expr(self, node):
|
||||||
|
while 1:
|
||||||
|
token_type = self.stream.current.type
|
||||||
|
if token_type == 'pipe':
|
||||||
|
node = self.parse_filter(node)
|
||||||
|
elif token_type == 'name' and self.stream.current.value == 'is':
|
||||||
|
node = self.parse_test(node)
|
||||||
|
# calls are valid both after postfix expressions (getattr
|
||||||
|
# and getitem) as well as filters and tests
|
||||||
|
elif token_type == 'lparen':
|
||||||
|
node = self.parse_call(node)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_subscript(self, node):
|
||||||
|
token = next(self.stream)
|
||||||
|
if token.type == 'dot':
|
||||||
|
attr_token = self.stream.current
|
||||||
|
next(self.stream)
|
||||||
|
if attr_token.type == 'name':
|
||||||
|
return nodes.Getattr(node, attr_token.value, 'load',
|
||||||
|
lineno=token.lineno)
|
||||||
|
elif attr_token.type != 'integer':
|
||||||
|
self.fail('expected name or number', attr_token.lineno)
|
||||||
|
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
|
||||||
|
return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
|
||||||
|
if token.type == 'lbracket':
|
||||||
|
args = []
|
||||||
|
while self.stream.current.type != 'rbracket':
|
||||||
|
if args:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
args.append(self.parse_subscribed())
|
||||||
|
self.stream.expect('rbracket')
|
||||||
|
if len(args) == 1:
|
||||||
|
arg = args[0]
|
||||||
|
else:
|
||||||
|
arg = nodes.Tuple(args, 'load', lineno=token.lineno)
|
||||||
|
return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
|
||||||
|
self.fail('expected subscript expression', self.lineno)
|
||||||
|
|
||||||
|
def parse_subscribed(self):
|
||||||
|
lineno = self.stream.current.lineno
|
||||||
|
|
||||||
|
if self.stream.current.type == 'colon':
|
||||||
|
next(self.stream)
|
||||||
|
args = [None]
|
||||||
|
else:
|
||||||
|
node = self.parse_expression()
|
||||||
|
if self.stream.current.type != 'colon':
|
||||||
|
return node
|
||||||
|
next(self.stream)
|
||||||
|
args = [node]
|
||||||
|
|
||||||
|
if self.stream.current.type == 'colon':
|
||||||
|
args.append(None)
|
||||||
|
elif self.stream.current.type not in ('rbracket', 'comma'):
|
||||||
|
args.append(self.parse_expression())
|
||||||
|
else:
|
||||||
|
args.append(None)
|
||||||
|
|
||||||
|
if self.stream.current.type == 'colon':
|
||||||
|
next(self.stream)
|
||||||
|
if self.stream.current.type not in ('rbracket', 'comma'):
|
||||||
|
args.append(self.parse_expression())
|
||||||
|
else:
|
||||||
|
args.append(None)
|
||||||
|
else:
|
||||||
|
args.append(None)
|
||||||
|
|
||||||
|
return nodes.Slice(lineno=lineno, *args)
|
||||||
|
|
||||||
|
def parse_call(self, node):
|
||||||
|
token = self.stream.expect('lparen')
|
||||||
|
args = []
|
||||||
|
kwargs = []
|
||||||
|
dyn_args = dyn_kwargs = None
|
||||||
|
require_comma = False
|
||||||
|
|
||||||
|
def ensure(expr):
|
||||||
|
if not expr:
|
||||||
|
self.fail('invalid syntax for function call expression',
|
||||||
|
token.lineno)
|
||||||
|
|
||||||
|
while self.stream.current.type != 'rparen':
|
||||||
|
if require_comma:
|
||||||
|
self.stream.expect('comma')
|
||||||
|
# support for trailing comma
|
||||||
|
if self.stream.current.type == 'rparen':
|
||||||
|
break
|
||||||
|
if self.stream.current.type == 'mul':
|
||||||
|
ensure(dyn_args is None and dyn_kwargs is None)
|
||||||
|
next(self.stream)
|
||||||
|
dyn_args = self.parse_expression()
|
||||||
|
elif self.stream.current.type == 'pow':
|
||||||
|
ensure(dyn_kwargs is None)
|
||||||
|
next(self.stream)
|
||||||
|
dyn_kwargs = self.parse_expression()
|
||||||
|
else:
|
||||||
|
ensure(dyn_args is None and dyn_kwargs is None)
|
||||||
|
if self.stream.current.type == 'name' and \
|
||||||
|
self.stream.look().type == 'assign':
|
||||||
|
key = self.stream.current.value
|
||||||
|
self.stream.skip(2)
|
||||||
|
value = self.parse_expression()
|
||||||
|
kwargs.append(nodes.Keyword(key, value,
|
||||||
|
lineno=value.lineno))
|
||||||
|
else:
|
||||||
|
ensure(not kwargs)
|
||||||
|
args.append(self.parse_expression())
|
||||||
|
|
||||||
|
require_comma = True
|
||||||
|
self.stream.expect('rparen')
|
||||||
|
|
||||||
|
if node is None:
|
||||||
|
return args, kwargs, dyn_args, dyn_kwargs
|
||||||
|
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
|
||||||
|
lineno=token.lineno)
|
||||||
|
|
||||||
|
def parse_filter(self, node, start_inline=False):
|
||||||
|
while self.stream.current.type == 'pipe' or start_inline:
|
||||||
|
if not start_inline:
|
||||||
|
next(self.stream)
|
||||||
|
token = self.stream.expect('name')
|
||||||
|
name = token.value
|
||||||
|
while self.stream.current.type == 'dot':
|
||||||
|
next(self.stream)
|
||||||
|
name += '.' + self.stream.expect('name').value
|
||||||
|
if self.stream.current.type == 'lparen':
|
||||||
|
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
|
||||||
|
else:
|
||||||
|
args = []
|
||||||
|
kwargs = []
|
||||||
|
dyn_args = dyn_kwargs = None
|
||||||
|
node = nodes.Filter(node, name, args, kwargs, dyn_args,
|
||||||
|
dyn_kwargs, lineno=token.lineno)
|
||||||
|
start_inline = False
|
||||||
|
return node
|
||||||
|
|
||||||
|
def parse_test(self, node):
|
||||||
|
token = next(self.stream)
|
||||||
|
if self.stream.current.test('name:not'):
|
||||||
|
next(self.stream)
|
||||||
|
negated = True
|
||||||
|
else:
|
||||||
|
negated = False
|
||||||
|
name = self.stream.expect('name').value
|
||||||
|
while self.stream.current.type == 'dot':
|
||||||
|
next(self.stream)
|
||||||
|
name += '.' + self.stream.expect('name').value
|
||||||
|
dyn_args = dyn_kwargs = None
|
||||||
|
kwargs = []
|
||||||
|
if self.stream.current.type == 'lparen':
|
||||||
|
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
|
||||||
|
elif self.stream.current.type in ('name', 'string', 'integer',
|
||||||
|
'float', 'lparen', 'lbracket',
|
||||||
|
'lbrace') and not \
|
||||||
|
self.stream.current.test_any('name:else', 'name:or',
|
||||||
|
'name:and'):
|
||||||
|
if self.stream.current.test('name:is'):
|
||||||
|
self.fail('You cannot chain multiple tests with is')
|
||||||
|
args = [self.parse_expression()]
|
||||||
|
else:
|
||||||
|
args = []
|
||||||
|
node = nodes.Test(node, name, args, kwargs, dyn_args,
|
||||||
|
dyn_kwargs, lineno=token.lineno)
|
||||||
|
if negated:
|
||||||
|
node = nodes.Not(node, lineno=token.lineno)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def subparse(self, end_tokens=None):
|
||||||
|
body = []
|
||||||
|
data_buffer = []
|
||||||
|
add_data = data_buffer.append
|
||||||
|
|
||||||
|
if end_tokens is not None:
|
||||||
|
self._end_token_stack.append(end_tokens)
|
||||||
|
|
||||||
|
def flush_data():
|
||||||
|
if data_buffer:
|
||||||
|
lineno = data_buffer[0].lineno
|
||||||
|
body.append(nodes.Output(data_buffer[:], lineno=lineno))
|
||||||
|
del data_buffer[:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
while self.stream:
|
||||||
|
token = self.stream.current
|
||||||
|
if token.type == 'data':
|
||||||
|
if token.value:
|
||||||
|
add_data(nodes.TemplateData(token.value,
|
||||||
|
lineno=token.lineno))
|
||||||
|
next(self.stream)
|
||||||
|
elif token.type == 'variable_begin':
|
||||||
|
next(self.stream)
|
||||||
|
add_data(self.parse_tuple(with_condexpr=True))
|
||||||
|
self.stream.expect('variable_end')
|
||||||
|
elif token.type == 'block_begin':
|
||||||
|
flush_data()
|
||||||
|
next(self.stream)
|
||||||
|
if end_tokens is not None and \
|
||||||
|
self.stream.current.test_any(*end_tokens):
|
||||||
|
return body
|
||||||
|
rv = self.parse_statement()
|
||||||
|
if isinstance(rv, list):
|
||||||
|
body.extend(rv)
|
||||||
|
else:
|
||||||
|
body.append(rv)
|
||||||
|
self.stream.expect('block_end')
|
||||||
|
else:
|
||||||
|
raise AssertionError('internal parsing error')
|
||||||
|
|
||||||
|
flush_data()
|
||||||
|
finally:
|
||||||
|
if end_tokens is not None:
|
||||||
|
self._end_token_stack.pop()
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""Parse the whole template into a `Template` node."""
|
||||||
|
result = nodes.Template(self.subparse(), lineno=1)
|
||||||
|
result.set_environment(self.environment)
|
||||||
|
return result
|
581
tools/external/jinja2/runtime.py
vendored
Normal file
581
tools/external/jinja2/runtime.py
vendored
Normal file
|
@ -0,0 +1,581 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.runtime
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Runtime helpers.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD.
|
||||||
|
"""
|
||||||
|
from itertools import chain
|
||||||
|
from jinja2.nodes import EvalContext, _context_function_types
|
||||||
|
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
|
||||||
|
internalcode, object_type_repr
|
||||||
|
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
||||||
|
TemplateNotFound
|
||||||
|
from jinja2._compat import next, imap, text_type, iteritems, \
|
||||||
|
implements_iterator, implements_to_string, string_types, PY2
|
||||||
|
|
||||||
|
|
||||||
|
# these variables are exported to the template runtime
|
||||||
|
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
|
||||||
|
'TemplateRuntimeError', 'missing', 'concat', 'escape',
|
||||||
|
'markup_join', 'unicode_join', 'to_string', 'identity',
|
||||||
|
'TemplateNotFound']
|
||||||
|
|
||||||
|
#: the name of the function that is used to convert something into
|
||||||
|
#: a string. We can just use the text type here.
|
||||||
|
to_string = text_type
|
||||||
|
|
||||||
|
#: the identity function. Useful for certain things in the environment
|
||||||
|
identity = lambda x: x
|
||||||
|
|
||||||
|
_last_iteration = object()
|
||||||
|
|
||||||
|
|
||||||
|
def markup_join(seq):
|
||||||
|
"""Concatenation that escapes if necessary and converts to unicode."""
|
||||||
|
buf = []
|
||||||
|
iterator = imap(soft_unicode, seq)
|
||||||
|
for arg in iterator:
|
||||||
|
buf.append(arg)
|
||||||
|
if hasattr(arg, '__html__'):
|
||||||
|
return Markup(u'').join(chain(buf, iterator))
|
||||||
|
return concat(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def unicode_join(seq):
|
||||||
|
"""Simple args to unicode conversion and concatenation."""
|
||||||
|
return concat(imap(text_type, seq))
|
||||||
|
|
||||||
|
|
||||||
|
def new_context(environment, template_name, blocks, vars=None,
|
||||||
|
shared=None, globals=None, locals=None):
|
||||||
|
"""Internal helper to for context creation."""
|
||||||
|
if vars is None:
|
||||||
|
vars = {}
|
||||||
|
if shared:
|
||||||
|
parent = vars
|
||||||
|
else:
|
||||||
|
parent = dict(globals or (), **vars)
|
||||||
|
if locals:
|
||||||
|
# if the parent is shared a copy should be created because
|
||||||
|
# we don't want to modify the dict passed
|
||||||
|
if shared:
|
||||||
|
parent = dict(parent)
|
||||||
|
for key, value in iteritems(locals):
|
||||||
|
if key[:2] == 'l_' and value is not missing:
|
||||||
|
parent[key[2:]] = value
|
||||||
|
return Context(environment, parent, template_name, blocks)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateReference(object):
|
||||||
|
"""The `self` in templates."""
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.__context = context
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
blocks = self.__context.blocks[name]
|
||||||
|
return BlockReference(name, self.__context, blocks, 0)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %r>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.__context.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Context(object):
|
||||||
|
"""The template context holds the variables of a template. It stores the
|
||||||
|
values passed to the template and also the names the template exports.
|
||||||
|
Creating instances is neither supported nor useful as it's created
|
||||||
|
automatically at various stages of the template evaluation and should not
|
||||||
|
be created by hand.
|
||||||
|
|
||||||
|
The context is immutable. Modifications on :attr:`parent` **must not**
|
||||||
|
happen and modifications on :attr:`vars` are allowed from generated
|
||||||
|
template code only. Template filters and global functions marked as
|
||||||
|
:func:`contextfunction`\s get the active context passed as first argument
|
||||||
|
and are allowed to access the context read-only.
|
||||||
|
|
||||||
|
The template context supports read only dict operations (`get`,
|
||||||
|
`keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
|
||||||
|
`__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
|
||||||
|
method that doesn't fail with a `KeyError` but returns an
|
||||||
|
:class:`Undefined` object for missing variables.
|
||||||
|
"""
|
||||||
|
__slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
|
||||||
|
'name', 'blocks', '__weakref__')
|
||||||
|
|
||||||
|
def __init__(self, environment, parent, name, blocks):
|
||||||
|
self.parent = parent
|
||||||
|
self.vars = {}
|
||||||
|
self.environment = environment
|
||||||
|
self.eval_ctx = EvalContext(self.environment, name)
|
||||||
|
self.exported_vars = set()
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# create the initial mapping of blocks. Whenever template inheritance
|
||||||
|
# takes place the runtime will update this mapping with the new blocks
|
||||||
|
# from the template.
|
||||||
|
self.blocks = dict((k, [v]) for k, v in iteritems(blocks))
|
||||||
|
|
||||||
|
def super(self, name, current):
|
||||||
|
"""Render a parent block."""
|
||||||
|
try:
|
||||||
|
blocks = self.blocks[name]
|
||||||
|
index = blocks.index(current) + 1
|
||||||
|
blocks[index]
|
||||||
|
except LookupError:
|
||||||
|
return self.environment.undefined('there is no parent block '
|
||||||
|
'called %r.' % name,
|
||||||
|
name='super')
|
||||||
|
return BlockReference(name, self, blocks, index)
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
"""Returns an item from the template context, if it doesn't exist
|
||||||
|
`default` is returned.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def resolve(self, key):
|
||||||
|
"""Looks up a variable like `__getitem__` or `get` but returns an
|
||||||
|
:class:`Undefined` object with the name of the name looked up.
|
||||||
|
"""
|
||||||
|
if key in self.vars:
|
||||||
|
return self.vars[key]
|
||||||
|
if key in self.parent:
|
||||||
|
return self.parent[key]
|
||||||
|
return self.environment.undefined(name=key)
|
||||||
|
|
||||||
|
def get_exported(self):
|
||||||
|
"""Get a new dict with the exported variables."""
|
||||||
|
return dict((k, self.vars[k]) for k in self.exported_vars)
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
"""Return a copy of the complete context as dict including the
|
||||||
|
exported variables.
|
||||||
|
"""
|
||||||
|
return dict(self.parent, **self.vars)
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def call(__self, __obj, *args, **kwargs):
|
||||||
|
"""Call the callable with the arguments and keyword arguments
|
||||||
|
provided but inject the active context or environment as first
|
||||||
|
argument if the callable is a :func:`contextfunction` or
|
||||||
|
:func:`environmentfunction`.
|
||||||
|
"""
|
||||||
|
if __debug__:
|
||||||
|
__traceback_hide__ = True
|
||||||
|
|
||||||
|
# Allow callable classes to take a context
|
||||||
|
fn = __obj.__call__
|
||||||
|
for fn_type in ('contextfunction',
|
||||||
|
'evalcontextfunction',
|
||||||
|
'environmentfunction'):
|
||||||
|
if hasattr(fn, fn_type):
|
||||||
|
__obj = fn
|
||||||
|
break
|
||||||
|
|
||||||
|
if isinstance(__obj, _context_function_types):
|
||||||
|
if getattr(__obj, 'contextfunction', 0):
|
||||||
|
args = (__self,) + args
|
||||||
|
elif getattr(__obj, 'evalcontextfunction', 0):
|
||||||
|
args = (__self.eval_ctx,) + args
|
||||||
|
elif getattr(__obj, 'environmentfunction', 0):
|
||||||
|
args = (__self.environment,) + args
|
||||||
|
try:
|
||||||
|
return __obj(*args, **kwargs)
|
||||||
|
except StopIteration:
|
||||||
|
return __self.environment.undefined('value was undefined because '
|
||||||
|
'a callable raised a '
|
||||||
|
'StopIteration exception')
|
||||||
|
|
||||||
|
def derived(self, locals=None):
|
||||||
|
"""Internal helper function to create a derived context."""
|
||||||
|
context = new_context(self.environment, self.name, {},
|
||||||
|
self.parent, True, None, locals)
|
||||||
|
context.vars.update(self.vars)
|
||||||
|
context.eval_ctx = self.eval_ctx
|
||||||
|
context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks))
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _all(meth):
|
||||||
|
proxy = lambda self: getattr(self.get_all(), meth)()
|
||||||
|
proxy.__doc__ = getattr(dict, meth).__doc__
|
||||||
|
proxy.__name__ = meth
|
||||||
|
return proxy
|
||||||
|
|
||||||
|
keys = _all('keys')
|
||||||
|
values = _all('values')
|
||||||
|
items = _all('items')
|
||||||
|
|
||||||
|
# not available on python 3
|
||||||
|
if PY2:
|
||||||
|
iterkeys = _all('iterkeys')
|
||||||
|
itervalues = _all('itervalues')
|
||||||
|
iteritems = _all('iteritems')
|
||||||
|
del _all
|
||||||
|
|
||||||
|
def __contains__(self, name):
|
||||||
|
return name in self.vars or name in self.parent
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""Lookup a variable or raise `KeyError` if the variable is
|
||||||
|
undefined.
|
||||||
|
"""
|
||||||
|
item = self.resolve(key)
|
||||||
|
if isinstance(item, Undefined):
|
||||||
|
raise KeyError(key)
|
||||||
|
return item
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %s of %r>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
repr(self.get_all()),
|
||||||
|
self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# register the context as mapping if possible
|
||||||
|
try:
|
||||||
|
from collections import Mapping
|
||||||
|
Mapping.register(Context)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BlockReference(object):
|
||||||
|
"""One block on a template reference."""
|
||||||
|
|
||||||
|
def __init__(self, name, context, stack, depth):
|
||||||
|
self.name = name
|
||||||
|
self._context = context
|
||||||
|
self._stack = stack
|
||||||
|
self._depth = depth
|
||||||
|
|
||||||
|
@property
|
||||||
|
def super(self):
|
||||||
|
"""Super the block."""
|
||||||
|
if self._depth + 1 >= len(self._stack):
|
||||||
|
return self._context.environment. \
|
||||||
|
undefined('there is no parent block called %r.' %
|
||||||
|
self.name, name='super')
|
||||||
|
return BlockReference(self.name, self._context, self._stack,
|
||||||
|
self._depth + 1)
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def __call__(self):
|
||||||
|
rv = concat(self._stack[self._depth](self._context))
|
||||||
|
if self._context.eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class LoopContext(object):
|
||||||
|
"""A loop context for dynamic iteration."""
|
||||||
|
|
||||||
|
def __init__(self, iterable, recurse=None, depth0=0):
|
||||||
|
self._iterator = iter(iterable)
|
||||||
|
self._recurse = recurse
|
||||||
|
self._after = self._safe_next()
|
||||||
|
self.index0 = -1
|
||||||
|
self.depth0 = depth0
|
||||||
|
|
||||||
|
# try to get the length of the iterable early. This must be done
|
||||||
|
# here because there are some broken iterators around where there
|
||||||
|
# __len__ is the number of iterations left (i'm looking at your
|
||||||
|
# listreverseiterator!).
|
||||||
|
try:
|
||||||
|
self._length = len(iterable)
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
self._length = None
|
||||||
|
|
||||||
|
def cycle(self, *args):
|
||||||
|
"""Cycles among the arguments with the current loop index."""
|
||||||
|
if not args:
|
||||||
|
raise TypeError('no items for cycling given')
|
||||||
|
return args[self.index0 % len(args)]
|
||||||
|
|
||||||
|
first = property(lambda x: x.index0 == 0)
|
||||||
|
last = property(lambda x: x._after is _last_iteration)
|
||||||
|
index = property(lambda x: x.index0 + 1)
|
||||||
|
revindex = property(lambda x: x.length - x.index0)
|
||||||
|
revindex0 = property(lambda x: x.length - x.index)
|
||||||
|
depth = property(lambda x: x.depth0 + 1)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return LoopContextIterator(self)
|
||||||
|
|
||||||
|
def _safe_next(self):
|
||||||
|
try:
|
||||||
|
return next(self._iterator)
|
||||||
|
except StopIteration:
|
||||||
|
return _last_iteration
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def loop(self, iterable):
|
||||||
|
if self._recurse is None:
|
||||||
|
raise TypeError('Tried to call non recursive loop. Maybe you '
|
||||||
|
"forgot the 'recursive' modifier.")
|
||||||
|
return self._recurse(iterable, self._recurse, self.depth0 + 1)
|
||||||
|
|
||||||
|
# a nifty trick to enhance the error message if someone tried to call
|
||||||
|
# the the loop without or with too many arguments.
|
||||||
|
__call__ = loop
|
||||||
|
del loop
|
||||||
|
|
||||||
|
@property
|
||||||
|
def length(self):
|
||||||
|
if self._length is None:
|
||||||
|
# if was not possible to get the length of the iterator when
|
||||||
|
# the loop context was created (ie: iterating over a generator)
|
||||||
|
# we have to convert the iterable into a sequence and use the
|
||||||
|
# length of that.
|
||||||
|
iterable = tuple(self._iterator)
|
||||||
|
self._iterator = iter(iterable)
|
||||||
|
self._length = len(iterable) + self.index0 + 1
|
||||||
|
return self._length
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %r/%r>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.index,
|
||||||
|
self.length
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@implements_iterator
|
||||||
|
class LoopContextIterator(object):
|
||||||
|
"""The iterator for a loop context."""
|
||||||
|
__slots__ = ('context',)
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
ctx = self.context
|
||||||
|
ctx.index0 += 1
|
||||||
|
if ctx._after is _last_iteration:
|
||||||
|
raise StopIteration()
|
||||||
|
next_elem = ctx._after
|
||||||
|
ctx._after = ctx._safe_next()
|
||||||
|
return next_elem, ctx
|
||||||
|
|
||||||
|
|
||||||
|
class Macro(object):
|
||||||
|
"""Wraps a macro function."""
|
||||||
|
|
||||||
|
def __init__(self, environment, func, name, arguments, defaults,
|
||||||
|
catch_kwargs, catch_varargs, caller):
|
||||||
|
self._environment = environment
|
||||||
|
self._func = func
|
||||||
|
self._argument_count = len(arguments)
|
||||||
|
self.name = name
|
||||||
|
self.arguments = arguments
|
||||||
|
self.defaults = defaults
|
||||||
|
self.catch_kwargs = catch_kwargs
|
||||||
|
self.catch_varargs = catch_varargs
|
||||||
|
self.caller = caller
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
# try to consume the positional arguments
|
||||||
|
arguments = list(args[:self._argument_count])
|
||||||
|
off = len(arguments)
|
||||||
|
|
||||||
|
# if the number of arguments consumed is not the number of
|
||||||
|
# arguments expected we start filling in keyword arguments
|
||||||
|
# and defaults.
|
||||||
|
if off != self._argument_count:
|
||||||
|
for idx, name in enumerate(self.arguments[len(arguments):]):
|
||||||
|
try:
|
||||||
|
value = kwargs.pop(name)
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
value = self.defaults[idx - self._argument_count + off]
|
||||||
|
except IndexError:
|
||||||
|
value = self._environment.undefined(
|
||||||
|
'parameter %r was not provided' % name, name=name)
|
||||||
|
arguments.append(value)
|
||||||
|
|
||||||
|
# it's important that the order of these arguments does not change
|
||||||
|
# if not also changed in the compiler's `function_scoping` method.
|
||||||
|
# the order is caller, keyword arguments, positional arguments!
|
||||||
|
if self.caller:
|
||||||
|
caller = kwargs.pop('caller', None)
|
||||||
|
if caller is None:
|
||||||
|
caller = self._environment.undefined('No caller defined',
|
||||||
|
name='caller')
|
||||||
|
arguments.append(caller)
|
||||||
|
if self.catch_kwargs:
|
||||||
|
arguments.append(kwargs)
|
||||||
|
elif kwargs:
|
||||||
|
raise TypeError('macro %r takes no keyword argument %r' %
|
||||||
|
(self.name, next(iter(kwargs))))
|
||||||
|
if self.catch_varargs:
|
||||||
|
arguments.append(args[self._argument_count:])
|
||||||
|
elif len(args) > self._argument_count:
|
||||||
|
raise TypeError('macro %r takes not more than %d argument(s)' %
|
||||||
|
(self.name, len(self.arguments)))
|
||||||
|
return self._func(*arguments)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %s>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.name is None and 'anonymous' or repr(self.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@implements_to_string
|
||||||
|
class Undefined(object):
|
||||||
|
"""The default undefined type. This undefined type can be printed and
|
||||||
|
iterated over, but every other access will raise an :exc:`UndefinedError`:
|
||||||
|
|
||||||
|
>>> foo = Undefined(name='foo')
|
||||||
|
>>> str(foo)
|
||||||
|
''
|
||||||
|
>>> not foo
|
||||||
|
True
|
||||||
|
>>> foo + 42
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
UndefinedError: 'foo' is undefined
|
||||||
|
"""
|
||||||
|
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
|
||||||
|
'_undefined_exception')
|
||||||
|
|
||||||
|
def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
|
||||||
|
self._undefined_hint = hint
|
||||||
|
self._undefined_obj = obj
|
||||||
|
self._undefined_name = name
|
||||||
|
self._undefined_exception = exc
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def _fail_with_undefined_error(self, *args, **kwargs):
|
||||||
|
"""Regular callback function for undefined objects that raises an
|
||||||
|
`UndefinedError` on call.
|
||||||
|
"""
|
||||||
|
if self._undefined_hint is None:
|
||||||
|
if self._undefined_obj is missing:
|
||||||
|
hint = '%r is undefined' % self._undefined_name
|
||||||
|
elif not isinstance(self._undefined_name, string_types):
|
||||||
|
hint = '%s has no element %r' % (
|
||||||
|
object_type_repr(self._undefined_obj),
|
||||||
|
self._undefined_name
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
hint = '%r has no attribute %r' % (
|
||||||
|
object_type_repr(self._undefined_obj),
|
||||||
|
self._undefined_name
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
hint = self._undefined_hint
|
||||||
|
raise self._undefined_exception(hint)
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name[:2] == '__':
|
||||||
|
raise AttributeError(name)
|
||||||
|
return self._fail_with_undefined_error()
|
||||||
|
|
||||||
|
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
|
||||||
|
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
|
||||||
|
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
|
||||||
|
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
|
||||||
|
__float__ = __complex__ = __pow__ = __rpow__ = \
|
||||||
|
_fail_with_undefined_error
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return type(self) is type(other)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return id(type(self))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return u''
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
if 0:
|
||||||
|
yield None
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Undefined'
|
||||||
|
|
||||||
|
|
||||||
|
@implements_to_string
|
||||||
|
class DebugUndefined(Undefined):
|
||||||
|
"""An undefined that returns the debug info when printed.
|
||||||
|
|
||||||
|
>>> foo = DebugUndefined(name='foo')
|
||||||
|
>>> str(foo)
|
||||||
|
'{{ foo }}'
|
||||||
|
>>> not foo
|
||||||
|
True
|
||||||
|
>>> foo + 42
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
UndefinedError: 'foo' is undefined
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self._undefined_hint is None:
|
||||||
|
if self._undefined_obj is missing:
|
||||||
|
return u'{{ %s }}' % self._undefined_name
|
||||||
|
return '{{ no such element: %s[%r] }}' % (
|
||||||
|
object_type_repr(self._undefined_obj),
|
||||||
|
self._undefined_name
|
||||||
|
)
|
||||||
|
return u'{{ undefined value printed: %s }}' % self._undefined_hint
|
||||||
|
|
||||||
|
|
||||||
|
@implements_to_string
|
||||||
|
class StrictUndefined(Undefined):
|
||||||
|
"""An undefined that barks on print and iteration as well as boolean
|
||||||
|
tests and all kinds of comparisons. In other words: you can do nothing
|
||||||
|
with it except checking if it's defined using the `defined` test.
|
||||||
|
|
||||||
|
>>> foo = StrictUndefined(name='foo')
|
||||||
|
>>> str(foo)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
UndefinedError: 'foo' is undefined
|
||||||
|
>>> not foo
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
UndefinedError: 'foo' is undefined
|
||||||
|
>>> foo + 42
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
UndefinedError: 'foo' is undefined
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
__iter__ = __str__ = __len__ = __nonzero__ = __eq__ = \
|
||||||
|
__ne__ = __bool__ = __hash__ = \
|
||||||
|
Undefined._fail_with_undefined_error
|
||||||
|
|
||||||
|
|
||||||
|
# remove remaining slots attributes, after the metaclass did the magic they
|
||||||
|
# are unneeded and irritating as they contain wrong data for the subclasses.
|
||||||
|
del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__
|
368
tools/external/jinja2/sandbox.py
vendored
Normal file
368
tools/external/jinja2/sandbox.py
vendored
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.sandbox
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Adds a sandbox layer to Jinja as it was the default behavior in the old
|
||||||
|
Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the
|
||||||
|
default behavior is easier to use.
|
||||||
|
|
||||||
|
The behavior can be changed by subclassing the environment.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD.
|
||||||
|
"""
|
||||||
|
import operator
|
||||||
|
from jinja2.environment import Environment
|
||||||
|
from jinja2.exceptions import SecurityError
|
||||||
|
from jinja2._compat import string_types, function_type, method_type, \
|
||||||
|
traceback_type, code_type, frame_type, generator_type, PY2
|
||||||
|
|
||||||
|
|
||||||
|
#: maximum number of items a range may produce
|
||||||
|
MAX_RANGE = 100000
|
||||||
|
|
||||||
|
#: attributes of function objects that are considered unsafe.
|
||||||
|
UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
|
||||||
|
'func_defaults', 'func_globals'])
|
||||||
|
|
||||||
|
#: unsafe method attributes. function attributes are unsafe for methods too
|
||||||
|
UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
|
||||||
|
|
||||||
|
#: unsafe generator attirbutes.
|
||||||
|
UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
|
||||||
|
|
||||||
|
# On versions > python 2 the special attributes on functions are gone,
|
||||||
|
# but they remain on methods and generators for whatever reason.
|
||||||
|
if not PY2:
|
||||||
|
UNSAFE_FUNCTION_ATTRIBUTES = set()
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
# make sure we don't warn in python 2.6 about stuff we don't care about
|
||||||
|
warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning,
|
||||||
|
module='jinja2.sandbox')
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
_mutable_set_types = (set,)
|
||||||
|
_mutable_mapping_types = (dict,)
|
||||||
|
_mutable_sequence_types = (list,)
|
||||||
|
|
||||||
|
|
||||||
|
# on python 2.x we can register the user collection types
|
||||||
|
try:
|
||||||
|
from UserDict import UserDict, DictMixin
|
||||||
|
from UserList import UserList
|
||||||
|
_mutable_mapping_types += (UserDict, DictMixin)
|
||||||
|
_mutable_set_types += (UserList,)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# if sets is still available, register the mutable set from there as well
|
||||||
|
try:
|
||||||
|
from sets import Set
|
||||||
|
_mutable_set_types += (Set,)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
#: register Python 2.6 abstract base classes
|
||||||
|
try:
|
||||||
|
from collections import MutableSet, MutableMapping, MutableSequence
|
||||||
|
_mutable_set_types += (MutableSet,)
|
||||||
|
_mutable_mapping_types += (MutableMapping,)
|
||||||
|
_mutable_sequence_types += (MutableSequence,)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
_mutable_spec = (
|
||||||
|
(_mutable_set_types, frozenset([
|
||||||
|
'add', 'clear', 'difference_update', 'discard', 'pop', 'remove',
|
||||||
|
'symmetric_difference_update', 'update'
|
||||||
|
])),
|
||||||
|
(_mutable_mapping_types, frozenset([
|
||||||
|
'clear', 'pop', 'popitem', 'setdefault', 'update'
|
||||||
|
])),
|
||||||
|
(_mutable_sequence_types, frozenset([
|
||||||
|
'append', 'reverse', 'insert', 'sort', 'extend', 'remove'
|
||||||
|
])),
|
||||||
|
(deque, frozenset([
|
||||||
|
'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop',
|
||||||
|
'popleft', 'remove', 'rotate'
|
||||||
|
]))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def safe_range(*args):
|
||||||
|
"""A range that can't generate ranges with a length of more than
|
||||||
|
MAX_RANGE items.
|
||||||
|
"""
|
||||||
|
rng = range(*args)
|
||||||
|
if len(rng) > MAX_RANGE:
|
||||||
|
raise OverflowError('range too big, maximum size for range is %d' %
|
||||||
|
MAX_RANGE)
|
||||||
|
return rng
|
||||||
|
|
||||||
|
|
||||||
|
def unsafe(f):
|
||||||
|
"""Marks a function or method as unsafe.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@unsafe
|
||||||
|
def delete(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
f.unsafe_callable = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def is_internal_attribute(obj, attr):
|
||||||
|
"""Test if the attribute given is an internal python attribute. For
|
||||||
|
example this function returns `True` for the `func_code` attribute of
|
||||||
|
python objects. This is useful if the environment method
|
||||||
|
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
|
||||||
|
|
||||||
|
>>> from jinja2.sandbox import is_internal_attribute
|
||||||
|
>>> is_internal_attribute(lambda: None, "func_code")
|
||||||
|
True
|
||||||
|
>>> is_internal_attribute((lambda x:x).func_code, 'co_code')
|
||||||
|
True
|
||||||
|
>>> is_internal_attribute(str, "upper")
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
if isinstance(obj, function_type):
|
||||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, method_type):
|
||||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
|
||||||
|
attr in UNSAFE_METHOD_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, type):
|
||||||
|
if attr == 'mro':
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, (code_type, traceback_type, frame_type)):
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, generator_type):
|
||||||
|
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
return attr.startswith('__')
|
||||||
|
|
||||||
|
|
||||||
|
def modifies_known_mutable(obj, attr):
|
||||||
|
"""This function checks if an attribute on a builtin mutable object
|
||||||
|
(list, dict, set or deque) would modify it if called. It also supports
|
||||||
|
the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and
|
||||||
|
with Python 2.6 onwards the abstract base classes `MutableSet`,
|
||||||
|
`MutableMapping`, and `MutableSequence`.
|
||||||
|
|
||||||
|
>>> modifies_known_mutable({}, "clear")
|
||||||
|
True
|
||||||
|
>>> modifies_known_mutable({}, "keys")
|
||||||
|
False
|
||||||
|
>>> modifies_known_mutable([], "append")
|
||||||
|
True
|
||||||
|
>>> modifies_known_mutable([], "index")
|
||||||
|
False
|
||||||
|
|
||||||
|
If called with an unsupported object (such as unicode) `False` is
|
||||||
|
returned.
|
||||||
|
|
||||||
|
>>> modifies_known_mutable("foo", "upper")
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
for typespec, unsafe in _mutable_spec:
|
||||||
|
if isinstance(obj, typespec):
|
||||||
|
return attr in unsafe
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxedEnvironment(Environment):
|
||||||
|
"""The sandboxed environment. It works like the regular environment but
|
||||||
|
tells the compiler to generate sandboxed code. Additionally subclasses of
|
||||||
|
this environment may override the methods that tell the runtime what
|
||||||
|
attributes or functions are safe to access.
|
||||||
|
|
||||||
|
If the template tries to access insecure code a :exc:`SecurityError` is
|
||||||
|
raised. However also other exceptions may occour during the rendering so
|
||||||
|
the caller has to ensure that all exceptions are catched.
|
||||||
|
"""
|
||||||
|
sandboxed = True
|
||||||
|
|
||||||
|
#: default callback table for the binary operators. A copy of this is
|
||||||
|
#: available on each instance of a sandboxed environment as
|
||||||
|
#: :attr:`binop_table`
|
||||||
|
default_binop_table = {
|
||||||
|
'+': operator.add,
|
||||||
|
'-': operator.sub,
|
||||||
|
'*': operator.mul,
|
||||||
|
'/': operator.truediv,
|
||||||
|
'//': operator.floordiv,
|
||||||
|
'**': operator.pow,
|
||||||
|
'%': operator.mod
|
||||||
|
}
|
||||||
|
|
||||||
|
#: default callback table for the unary operators. A copy of this is
|
||||||
|
#: available on each instance of a sandboxed environment as
|
||||||
|
#: :attr:`unop_table`
|
||||||
|
default_unop_table = {
|
||||||
|
'+': operator.pos,
|
||||||
|
'-': operator.neg
|
||||||
|
}
|
||||||
|
|
||||||
|
#: a set of binary operators that should be intercepted. Each operator
|
||||||
|
#: that is added to this set (empty by default) is delegated to the
|
||||||
|
#: :meth:`call_binop` method that will perform the operator. The default
|
||||||
|
#: operator callback is specified by :attr:`binop_table`.
|
||||||
|
#:
|
||||||
|
#: The following binary operators are interceptable:
|
||||||
|
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
||||||
|
#:
|
||||||
|
#: The default operation form the operator table corresponds to the
|
||||||
|
#: builtin function. Intercepted calls are always slower than the native
|
||||||
|
#: operator call, so make sure only to intercept the ones you are
|
||||||
|
#: interested in.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.6
|
||||||
|
intercepted_binops = frozenset()
|
||||||
|
|
||||||
|
#: a set of unary operators that should be intercepted. Each operator
|
||||||
|
#: that is added to this set (empty by default) is delegated to the
|
||||||
|
#: :meth:`call_unop` method that will perform the operator. The default
|
||||||
|
#: operator callback is specified by :attr:`unop_table`.
|
||||||
|
#:
|
||||||
|
#: The following unary operators are interceptable: ``+``, ``-``
|
||||||
|
#:
|
||||||
|
#: The default operation form the operator table corresponds to the
|
||||||
|
#: builtin function. Intercepted calls are always slower than the native
|
||||||
|
#: operator call, so make sure only to intercept the ones you are
|
||||||
|
#: interested in.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.6
|
||||||
|
intercepted_unops = frozenset()
|
||||||
|
|
||||||
|
def intercept_unop(self, operator):
|
||||||
|
"""Called during template compilation with the name of a unary
|
||||||
|
operator to check if it should be intercepted at runtime. If this
|
||||||
|
method returns `True`, :meth:`call_unop` is excuted for this unary
|
||||||
|
operator. The default implementation of :meth:`call_unop` will use
|
||||||
|
the :attr:`unop_table` dictionary to perform the operator with the
|
||||||
|
same logic as the builtin one.
|
||||||
|
|
||||||
|
The following unary operators are interceptable: ``+`` and ``-``
|
||||||
|
|
||||||
|
Intercepted calls are always slower than the native operator call,
|
||||||
|
so make sure only to intercept the ones you are interested in.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
Environment.__init__(self, *args, **kwargs)
|
||||||
|
self.globals['range'] = safe_range
|
||||||
|
self.binop_table = self.default_binop_table.copy()
|
||||||
|
self.unop_table = self.default_unop_table.copy()
|
||||||
|
|
||||||
|
def is_safe_attribute(self, obj, attr, value):
|
||||||
|
"""The sandboxed environment will call this method to check if the
|
||||||
|
attribute of an object is safe to access. Per default all attributes
|
||||||
|
starting with an underscore are considered private as well as the
|
||||||
|
special attributes of internal python objects as returned by the
|
||||||
|
:func:`is_internal_attribute` function.
|
||||||
|
"""
|
||||||
|
return not (attr.startswith('_') or is_internal_attribute(obj, attr))
|
||||||
|
|
||||||
|
def is_safe_callable(self, obj):
|
||||||
|
"""Check if an object is safely callable. Per default a function is
|
||||||
|
considered safe unless the `unsafe_callable` attribute exists and is
|
||||||
|
True. Override this method to alter the behavior, but this won't
|
||||||
|
affect the `unsafe` decorator from this module.
|
||||||
|
"""
|
||||||
|
return not (getattr(obj, 'unsafe_callable', False) or
|
||||||
|
getattr(obj, 'alters_data', False))
|
||||||
|
|
||||||
|
def call_binop(self, context, operator, left, right):
|
||||||
|
"""For intercepted binary operator calls (:meth:`intercepted_binops`)
|
||||||
|
this function is executed instead of the builtin operator. This can
|
||||||
|
be used to fine tune the behavior of certain operators.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return self.binop_table[operator](left, right)
|
||||||
|
|
||||||
|
def call_unop(self, context, operator, arg):
|
||||||
|
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
|
||||||
|
this function is executed instead of the builtin operator. This can
|
||||||
|
be used to fine tune the behavior of certain operators.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return self.unop_table[operator](arg)
|
||||||
|
|
||||||
|
def getitem(self, obj, argument):
|
||||||
|
"""Subscribe an object from sandboxed code."""
|
||||||
|
try:
|
||||||
|
return obj[argument]
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
if isinstance(argument, string_types):
|
||||||
|
try:
|
||||||
|
attr = str(argument)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = getattr(obj, attr)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.is_safe_attribute(obj, argument, value):
|
||||||
|
return value
|
||||||
|
return self.unsafe_undefined(obj, argument)
|
||||||
|
return self.undefined(obj=obj, name=argument)
|
||||||
|
|
||||||
|
def getattr(self, obj, attribute):
|
||||||
|
"""Subscribe an object from sandboxed code and prefer the
|
||||||
|
attribute. The attribute passed *must* be a bytestring.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
value = getattr(obj, attribute)
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
return obj[attribute]
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.is_safe_attribute(obj, attribute, value):
|
||||||
|
return value
|
||||||
|
return self.unsafe_undefined(obj, attribute)
|
||||||
|
return self.undefined(obj=obj, name=attribute)
|
||||||
|
|
||||||
|
def unsafe_undefined(self, obj, attribute):
|
||||||
|
"""Return an undefined object for unsafe attributes."""
|
||||||
|
return self.undefined('access to attribute %r of %r '
|
||||||
|
'object is unsafe.' % (
|
||||||
|
attribute,
|
||||||
|
obj.__class__.__name__
|
||||||
|
), name=attribute, obj=obj, exc=SecurityError)
|
||||||
|
|
||||||
|
def call(__self, __context, __obj, *args, **kwargs):
|
||||||
|
"""Call an object from sandboxed code."""
|
||||||
|
# the double prefixes are to avoid double keyword argument
|
||||||
|
# errors when proxying the call.
|
||||||
|
if not __self.is_safe_callable(__obj):
|
||||||
|
raise SecurityError('%r is not safely callable' % (__obj,))
|
||||||
|
return __context.call(__obj, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
||||||
|
"""Works exactly like the regular `SandboxedEnvironment` but does not
|
||||||
|
permit modifications on the builtin mutable objects `list`, `set`, and
|
||||||
|
`dict` by using the :func:`modifies_known_mutable` function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_safe_attribute(self, obj, attr, value):
|
||||||
|
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
|
||||||
|
return False
|
||||||
|
return not modifies_known_mutable(obj, attr)
|
149
tools/external/jinja2/tests.py
vendored
Normal file
149
tools/external/jinja2/tests.py
vendored
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.tests
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Jinja test functions. Used with the "is" operator.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
from jinja2.runtime import Undefined
|
||||||
|
from jinja2._compat import text_type, string_types, mapping_types
|
||||||
|
|
||||||
|
|
||||||
|
number_re = re.compile(r'^-?\d+(\.\d+)?$')
|
||||||
|
regex_type = type(number_re)
|
||||||
|
|
||||||
|
|
||||||
|
test_callable = callable
|
||||||
|
|
||||||
|
|
||||||
|
def test_odd(value):
|
||||||
|
"""Return true if the variable is odd."""
|
||||||
|
return value % 2 == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_even(value):
|
||||||
|
"""Return true if the variable is even."""
|
||||||
|
return value % 2 == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_divisibleby(value, num):
|
||||||
|
"""Check if a variable is divisible by a number."""
|
||||||
|
return value % num == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_defined(value):
|
||||||
|
"""Return true if the variable is defined:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% if variable is defined %}
|
||||||
|
value of variable: {{ variable }}
|
||||||
|
{% else %}
|
||||||
|
variable is not defined
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
See the :func:`default` filter for a simple way to set undefined
|
||||||
|
variables.
|
||||||
|
"""
|
||||||
|
return not isinstance(value, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def test_undefined(value):
|
||||||
|
"""Like :func:`defined` but the other way round."""
|
||||||
|
return isinstance(value, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def test_none(value):
|
||||||
|
"""Return true if the variable is none."""
|
||||||
|
return value is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_lower(value):
|
||||||
|
"""Return true if the variable is lowercased."""
|
||||||
|
return text_type(value).islower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_upper(value):
|
||||||
|
"""Return true if the variable is uppercased."""
|
||||||
|
return text_type(value).isupper()
|
||||||
|
|
||||||
|
|
||||||
|
def test_string(value):
|
||||||
|
"""Return true if the object is a string."""
|
||||||
|
return isinstance(value, string_types)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mapping(value):
|
||||||
|
"""Return true if the object is a mapping (dict etc.).
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return isinstance(value, mapping_types)
|
||||||
|
|
||||||
|
|
||||||
|
def test_number(value):
|
||||||
|
"""Return true if the variable is a number."""
|
||||||
|
return isinstance(value, (int, float, complex))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence(value):
|
||||||
|
"""Return true if the variable is a sequence. Sequences are variables
|
||||||
|
that are iterable.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
len(value)
|
||||||
|
value.__getitem__
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_sameas(value, other):
|
||||||
|
"""Check if an object points to the same memory address than another
|
||||||
|
object:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% if foo.attribute is sameas false %}
|
||||||
|
the foo attribute really is the `False` singleton
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
return value is other
|
||||||
|
|
||||||
|
|
||||||
|
def test_iterable(value):
|
||||||
|
"""Check if it's possible to iterate over an object."""
|
||||||
|
try:
|
||||||
|
iter(value)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped(value):
|
||||||
|
"""Check if the value is escaped."""
|
||||||
|
return hasattr(value, '__html__')
|
||||||
|
|
||||||
|
|
||||||
|
TESTS = {
|
||||||
|
'odd': test_odd,
|
||||||
|
'even': test_even,
|
||||||
|
'divisibleby': test_divisibleby,
|
||||||
|
'defined': test_defined,
|
||||||
|
'undefined': test_undefined,
|
||||||
|
'none': test_none,
|
||||||
|
'lower': test_lower,
|
||||||
|
'upper': test_upper,
|
||||||
|
'string': test_string,
|
||||||
|
'mapping': test_mapping,
|
||||||
|
'number': test_number,
|
||||||
|
'sequence': test_sequence,
|
||||||
|
'iterable': test_iterable,
|
||||||
|
'callable': test_callable,
|
||||||
|
'sameas': test_sameas,
|
||||||
|
'escaped': test_escaped
|
||||||
|
}
|
520
tools/external/jinja2/utils.py
vendored
Normal file
520
tools/external/jinja2/utils.py
vendored
Normal file
|
@ -0,0 +1,520 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.utils
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Utility functions.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import errno
|
||||||
|
from collections import deque
|
||||||
|
from jinja2._compat import text_type, string_types, implements_iterator, \
|
||||||
|
allocate_lock, url_quote
|
||||||
|
|
||||||
|
|
||||||
|
_word_split_re = re.compile(r'(\s+)')
|
||||||
|
_punctuation_re = re.compile(
|
||||||
|
'^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
|
||||||
|
'|'.join(map(re.escape, ('(', '<', '<'))),
|
||||||
|
'|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>')))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
|
||||||
|
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
||||||
|
_entity_re = re.compile(r'&([^;]+);')
|
||||||
|
_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
_digits = '0123456789'
|
||||||
|
|
||||||
|
# special singleton representing missing values for the runtime
|
||||||
|
missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
|
||||||
|
|
||||||
|
# internal code
|
||||||
|
internal_code = set()
|
||||||
|
|
||||||
|
concat = u''.join
|
||||||
|
|
||||||
|
|
||||||
|
def contextfunction(f):
|
||||||
|
"""This decorator can be used to mark a function or method context callable.
|
||||||
|
A context callable is passed the active :class:`Context` as first argument when
|
||||||
|
called from the template. This is useful if a function wants to get access
|
||||||
|
to the context or functions provided on the context object. For example
|
||||||
|
a function that returns a sorted list of template variables the current
|
||||||
|
template exports could look like this::
|
||||||
|
|
||||||
|
@contextfunction
|
||||||
|
def get_exported_names(context):
|
||||||
|
return sorted(context.exported_vars)
|
||||||
|
"""
|
||||||
|
f.contextfunction = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def evalcontextfunction(f):
|
||||||
|
"""This decorator can be used to mark a function or method as an eval
|
||||||
|
context callable. This is similar to the :func:`contextfunction`
|
||||||
|
but instead of passing the context, an evaluation context object is
|
||||||
|
passed. For more information about the eval context, see
|
||||||
|
:ref:`eval-context`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.4
|
||||||
|
"""
|
||||||
|
f.evalcontextfunction = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def environmentfunction(f):
|
||||||
|
"""This decorator can be used to mark a function or method as environment
|
||||||
|
callable. This decorator works exactly like the :func:`contextfunction`
|
||||||
|
decorator just that the first argument is the active :class:`Environment`
|
||||||
|
and not context.
|
||||||
|
"""
|
||||||
|
f.environmentfunction = True
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def internalcode(f):
|
||||||
|
"""Marks the function as internally used"""
|
||||||
|
internal_code.add(f.__code__)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def is_undefined(obj):
|
||||||
|
"""Check if the object passed is undefined. This does nothing more than
|
||||||
|
performing an instance check against :class:`Undefined` but looks nicer.
|
||||||
|
This can be used for custom filters or tests that want to react to
|
||||||
|
undefined variables. For example a custom default filter can look like
|
||||||
|
this::
|
||||||
|
|
||||||
|
def default(var, default=''):
|
||||||
|
if is_undefined(var):
|
||||||
|
return default
|
||||||
|
return var
|
||||||
|
"""
|
||||||
|
from jinja2.runtime import Undefined
|
||||||
|
return isinstance(obj, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def consume(iterable):
|
||||||
|
"""Consumes an iterable without doing anything with it."""
|
||||||
|
for event in iterable:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def clear_caches():
|
||||||
|
"""Jinja2 keeps internal caches for environments and lexers. These are
|
||||||
|
used so that Jinja2 doesn't have to recreate environments and lexers all
|
||||||
|
the time. Normally you don't have to care about that but if you are
|
||||||
|
messuring memory consumption you may want to clean the caches.
|
||||||
|
"""
|
||||||
|
from jinja2.environment import _spontaneous_environments
|
||||||
|
from jinja2.lexer import _lexer_cache
|
||||||
|
_spontaneous_environments.clear()
|
||||||
|
_lexer_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def import_string(import_name, silent=False):
|
||||||
|
"""Imports an object based on a string. This is useful if you want to
|
||||||
|
use import paths as endpoints or something similar. An import path can
|
||||||
|
be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
||||||
|
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
||||||
|
|
||||||
|
If the `silent` is True the return value will be `None` if the import
|
||||||
|
fails.
|
||||||
|
|
||||||
|
:return: imported object
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if ':' in import_name:
|
||||||
|
module, obj = import_name.split(':', 1)
|
||||||
|
elif '.' in import_name:
|
||||||
|
items = import_name.split('.')
|
||||||
|
module = '.'.join(items[:-1])
|
||||||
|
obj = items[-1]
|
||||||
|
else:
|
||||||
|
return __import__(import_name)
|
||||||
|
return getattr(__import__(module, None, None, [obj]), obj)
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
if not silent:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def open_if_exists(filename, mode='rb'):
|
||||||
|
"""Returns a file descriptor for the filename if that file exists,
|
||||||
|
otherwise `None`.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return open(filename, mode)
|
||||||
|
except IOError as e:
|
||||||
|
if e.errno not in (errno.ENOENT, errno.EISDIR):
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def object_type_repr(obj):
|
||||||
|
"""Returns the name of the object's type. For some recognized
|
||||||
|
singletons the name of the object is returned instead. (For
|
||||||
|
example for `None` and `Ellipsis`).
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return 'None'
|
||||||
|
elif obj is Ellipsis:
|
||||||
|
return 'Ellipsis'
|
||||||
|
# __builtin__ in 2.x, builtins in 3.x
|
||||||
|
if obj.__class__.__module__ in ('__builtin__', 'builtins'):
|
||||||
|
name = obj.__class__.__name__
|
||||||
|
else:
|
||||||
|
name = obj.__class__.__module__ + '.' + obj.__class__.__name__
|
||||||
|
return '%s object' % name
|
||||||
|
|
||||||
|
|
||||||
|
def pformat(obj, verbose=False):
|
||||||
|
"""Prettyprint an object. Either use the `pretty` library or the
|
||||||
|
builtin `pprint`.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from pretty import pretty
|
||||||
|
return pretty(obj, verbose=verbose)
|
||||||
|
except ImportError:
|
||||||
|
from pprint import pformat
|
||||||
|
return pformat(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def urlize(text, trim_url_limit=None, nofollow=False):
|
||||||
|
"""Converts any URLs in text into clickable links. Works on http://,
|
||||||
|
https:// and www. links. Links can have trailing punctuation (periods,
|
||||||
|
commas, close-parens) and leading punctuation (opening parens) and
|
||||||
|
it'll still do the right thing.
|
||||||
|
|
||||||
|
If trim_url_limit is not None, the URLs in link text will be limited
|
||||||
|
to trim_url_limit characters.
|
||||||
|
|
||||||
|
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
||||||
|
attribute.
|
||||||
|
"""
|
||||||
|
trim_url = lambda x, limit=trim_url_limit: limit is not None \
|
||||||
|
and (x[:limit] + (len(x) >=limit and '...'
|
||||||
|
or '')) or x
|
||||||
|
words = _word_split_re.split(text_type(escape(text)))
|
||||||
|
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
||||||
|
for i, word in enumerate(words):
|
||||||
|
match = _punctuation_re.match(word)
|
||||||
|
if match:
|
||||||
|
lead, middle, trail = match.groups()
|
||||||
|
if middle.startswith('www.') or (
|
||||||
|
'@' not in middle and
|
||||||
|
not middle.startswith('http://') and
|
||||||
|
not middle.startswith('https://') and
|
||||||
|
len(middle) > 0 and
|
||||||
|
middle[0] in _letters + _digits and (
|
||||||
|
middle.endswith('.org') or
|
||||||
|
middle.endswith('.net') or
|
||||||
|
middle.endswith('.com')
|
||||||
|
)):
|
||||||
|
middle = '<a href="http://%s"%s>%s</a>' % (middle,
|
||||||
|
nofollow_attr, trim_url(middle))
|
||||||
|
if middle.startswith('http://') or \
|
||||||
|
middle.startswith('https://'):
|
||||||
|
middle = '<a href="%s"%s>%s</a>' % (middle,
|
||||||
|
nofollow_attr, trim_url(middle))
|
||||||
|
if '@' in middle and not middle.startswith('www.') and \
|
||||||
|
not ':' in middle and _simple_email_re.match(middle):
|
||||||
|
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
|
||||||
|
if lead + middle + trail != word:
|
||||||
|
words[i] = lead + middle + trail
|
||||||
|
return u''.join(words)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
|
||||||
|
"""Generate some lorem impsum for the template."""
|
||||||
|
from jinja2.constants import LOREM_IPSUM_WORDS
|
||||||
|
from random import choice, randrange
|
||||||
|
words = LOREM_IPSUM_WORDS.split()
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for _ in range(n):
|
||||||
|
next_capitalized = True
|
||||||
|
last_comma = last_fullstop = 0
|
||||||
|
word = None
|
||||||
|
last = None
|
||||||
|
p = []
|
||||||
|
|
||||||
|
# each paragraph contains out of 20 to 100 words.
|
||||||
|
for idx, _ in enumerate(range(randrange(min, max))):
|
||||||
|
while True:
|
||||||
|
word = choice(words)
|
||||||
|
if word != last:
|
||||||
|
last = word
|
||||||
|
break
|
||||||
|
if next_capitalized:
|
||||||
|
word = word.capitalize()
|
||||||
|
next_capitalized = False
|
||||||
|
# add commas
|
||||||
|
if idx - randrange(3, 8) > last_comma:
|
||||||
|
last_comma = idx
|
||||||
|
last_fullstop += 2
|
||||||
|
word += ','
|
||||||
|
# add end of sentences
|
||||||
|
if idx - randrange(10, 20) > last_fullstop:
|
||||||
|
last_comma = last_fullstop = idx
|
||||||
|
word += '.'
|
||||||
|
next_capitalized = True
|
||||||
|
p.append(word)
|
||||||
|
|
||||||
|
# ensure that the paragraph ends with a dot.
|
||||||
|
p = u' '.join(p)
|
||||||
|
if p.endswith(','):
|
||||||
|
p = p[:-1] + '.'
|
||||||
|
elif not p.endswith('.'):
|
||||||
|
p += '.'
|
||||||
|
result.append(p)
|
||||||
|
|
||||||
|
if not html:
|
||||||
|
return u'\n\n'.join(result)
|
||||||
|
return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
|
||||||
|
|
||||||
|
|
||||||
|
def unicode_urlencode(obj, charset='utf-8'):
|
||||||
|
"""URL escapes a single bytestring or unicode string with the
|
||||||
|
given charset if applicable to URL safe quoting under all rules
|
||||||
|
that need to be considered under all supported Python versions.
|
||||||
|
|
||||||
|
If non strings are provided they are converted to their unicode
|
||||||
|
representation first.
|
||||||
|
"""
|
||||||
|
if not isinstance(obj, string_types):
|
||||||
|
obj = text_type(obj)
|
||||||
|
if isinstance(obj, text_type):
|
||||||
|
obj = obj.encode(charset)
|
||||||
|
return text_type(url_quote(obj))
|
||||||
|
|
||||||
|
|
||||||
|
class LRUCache(object):
|
||||||
|
"""A simple LRU Cache implementation."""
|
||||||
|
|
||||||
|
# this is fast for small capacities (something below 1000) but doesn't
|
||||||
|
# scale. But as long as it's only used as storage for templates this
|
||||||
|
# won't do any harm.
|
||||||
|
|
||||||
|
def __init__(self, capacity):
|
||||||
|
self.capacity = capacity
|
||||||
|
self._mapping = {}
|
||||||
|
self._queue = deque()
|
||||||
|
self._postinit()
|
||||||
|
|
||||||
|
def _postinit(self):
|
||||||
|
# alias all queue methods for faster lookup
|
||||||
|
self._popleft = self._queue.popleft
|
||||||
|
self._pop = self._queue.pop
|
||||||
|
self._remove = self._queue.remove
|
||||||
|
self._wlock = allocate_lock()
|
||||||
|
self._append = self._queue.append
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return {
|
||||||
|
'capacity': self.capacity,
|
||||||
|
'_mapping': self._mapping,
|
||||||
|
'_queue': self._queue
|
||||||
|
}
|
||||||
|
|
||||||
|
def __setstate__(self, d):
|
||||||
|
self.__dict__.update(d)
|
||||||
|
self._postinit()
|
||||||
|
|
||||||
|
def __getnewargs__(self):
|
||||||
|
return (self.capacity,)
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"""Return a shallow copy of the instance."""
|
||||||
|
rv = self.__class__(self.capacity)
|
||||||
|
rv._mapping.update(self._mapping)
|
||||||
|
rv._queue = deque(self._queue)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
"""Return an item from the cache dict or `default`"""
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def setdefault(self, key, default=None):
|
||||||
|
"""Set `default` if the key is not in the cache otherwise
|
||||||
|
leave unchanged. Return the value of this key.
|
||||||
|
"""
|
||||||
|
self._wlock.acquire()
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
self[key] = default
|
||||||
|
return default
|
||||||
|
finally:
|
||||||
|
self._wlock.release()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Clear the cache."""
|
||||||
|
self._wlock.acquire()
|
||||||
|
try:
|
||||||
|
self._mapping.clear()
|
||||||
|
self._queue.clear()
|
||||||
|
finally:
|
||||||
|
self._wlock.release()
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
"""Check if a key exists in this cache."""
|
||||||
|
return key in self._mapping
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Return the current size of the cache."""
|
||||||
|
return len(self._mapping)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %r>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self._mapping
|
||||||
|
)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""Get an item from the cache. Moves the item up so that it has the
|
||||||
|
highest priority then.
|
||||||
|
|
||||||
|
Raise a `KeyError` if it does not exist.
|
||||||
|
"""
|
||||||
|
self._wlock.acquire()
|
||||||
|
try:
|
||||||
|
rv = self._mapping[key]
|
||||||
|
if self._queue[-1] != key:
|
||||||
|
try:
|
||||||
|
self._remove(key)
|
||||||
|
except ValueError:
|
||||||
|
# if something removed the key from the container
|
||||||
|
# when we read, ignore the ValueError that we would
|
||||||
|
# get otherwise.
|
||||||
|
pass
|
||||||
|
self._append(key)
|
||||||
|
return rv
|
||||||
|
finally:
|
||||||
|
self._wlock.release()
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
"""Sets the value for an item. Moves the item up so that it
|
||||||
|
has the highest priority then.
|
||||||
|
"""
|
||||||
|
self._wlock.acquire()
|
||||||
|
try:
|
||||||
|
if key in self._mapping:
|
||||||
|
self._remove(key)
|
||||||
|
elif len(self._mapping) == self.capacity:
|
||||||
|
del self._mapping[self._popleft()]
|
||||||
|
self._append(key)
|
||||||
|
self._mapping[key] = value
|
||||||
|
finally:
|
||||||
|
self._wlock.release()
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
"""Remove an item from the cache dict.
|
||||||
|
Raise a `KeyError` if it does not exist.
|
||||||
|
"""
|
||||||
|
self._wlock.acquire()
|
||||||
|
try:
|
||||||
|
del self._mapping[key]
|
||||||
|
try:
|
||||||
|
self._remove(key)
|
||||||
|
except ValueError:
|
||||||
|
# __getitem__ is not locked, it might happen
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self._wlock.release()
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
"""Return a list of items."""
|
||||||
|
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
||||||
|
result.reverse()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
"""Iterate over all items."""
|
||||||
|
return iter(self.items())
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
"""Return a list of all values."""
|
||||||
|
return [x[1] for x in self.items()]
|
||||||
|
|
||||||
|
def itervalue(self):
|
||||||
|
"""Iterate over all values."""
|
||||||
|
return iter(self.values())
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
"""Return a list of all keys ordered by most recent usage."""
|
||||||
|
return list(self)
|
||||||
|
|
||||||
|
def iterkeys(self):
|
||||||
|
"""Iterate over all keys in the cache dict, ordered by
|
||||||
|
the most recent usage.
|
||||||
|
"""
|
||||||
|
return reversed(tuple(self._queue))
|
||||||
|
|
||||||
|
__iter__ = iterkeys
|
||||||
|
|
||||||
|
def __reversed__(self):
|
||||||
|
"""Iterate over the values in the cache dict, oldest items
|
||||||
|
coming first.
|
||||||
|
"""
|
||||||
|
return iter(tuple(self._queue))
|
||||||
|
|
||||||
|
__copy__ = copy
|
||||||
|
|
||||||
|
|
||||||
|
# register the LRU cache as mutable mapping if possible
|
||||||
|
try:
|
||||||
|
from collections import MutableMapping
|
||||||
|
MutableMapping.register(LRUCache)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@implements_iterator
|
||||||
|
class Cycler(object):
|
||||||
|
"""A cycle helper for templates."""
|
||||||
|
|
||||||
|
def __init__(self, *items):
|
||||||
|
if not items:
|
||||||
|
raise RuntimeError('at least one item has to be provided')
|
||||||
|
self.items = items
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Resets the cycle."""
|
||||||
|
self.pos = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current(self):
|
||||||
|
"""Returns the current item."""
|
||||||
|
return self.items[self.pos]
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
"""Goes one item ahead and returns it."""
|
||||||
|
rv = self.current
|
||||||
|
self.pos = (self.pos + 1) % len(self.items)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class Joiner(object):
|
||||||
|
"""A joining helper for templates."""
|
||||||
|
|
||||||
|
def __init__(self, sep=u', '):
|
||||||
|
self.sep = sep
|
||||||
|
self.used = False
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
if not self.used:
|
||||||
|
self.used = True
|
||||||
|
return u''
|
||||||
|
return self.sep
|
||||||
|
|
||||||
|
|
||||||
|
# Imported here because that's where it was in the past
|
||||||
|
from markupsafe import Markup, escape, soft_unicode
|
87
tools/external/jinja2/visitor.py
vendored
Normal file
87
tools/external/jinja2/visitor.py
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
jinja2.visitor
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module implements a visitor for the nodes.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by the Jinja Team.
|
||||||
|
:license: BSD.
|
||||||
|
"""
|
||||||
|
from jinja2.nodes import Node
|
||||||
|
|
||||||
|
|
||||||
|
class NodeVisitor(object):
|
||||||
|
"""Walks the abstract syntax tree and call visitor functions for every
|
||||||
|
node found. The visitor functions may return values which will be
|
||||||
|
forwarded by the `visit` method.
|
||||||
|
|
||||||
|
Per default the visitor functions for the nodes are ``'visit_'`` +
|
||||||
|
class name of the node. So a `TryFinally` node visit function would
|
||||||
|
be `visit_TryFinally`. This behavior can be changed by overriding
|
||||||
|
the `get_visitor` function. If no visitor function exists for a node
|
||||||
|
(return value `None`) the `generic_visit` visitor is used instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_visitor(self, node):
|
||||||
|
"""Return the visitor function for this node or `None` if no visitor
|
||||||
|
exists for this node. In that case the generic visit function is
|
||||||
|
used instead.
|
||||||
|
"""
|
||||||
|
method = 'visit_' + node.__class__.__name__
|
||||||
|
return getattr(self, method, None)
|
||||||
|
|
||||||
|
def visit(self, node, *args, **kwargs):
|
||||||
|
"""Visit a node."""
|
||||||
|
f = self.get_visitor(node)
|
||||||
|
if f is not None:
|
||||||
|
return f(node, *args, **kwargs)
|
||||||
|
return self.generic_visit(node, *args, **kwargs)
|
||||||
|
|
||||||
|
def generic_visit(self, node, *args, **kwargs):
|
||||||
|
"""Called if no explicit visitor function exists for a node."""
|
||||||
|
for node in node.iter_child_nodes():
|
||||||
|
self.visit(node, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeTransformer(NodeVisitor):
|
||||||
|
"""Walks the abstract syntax tree and allows modifications of nodes.
|
||||||
|
|
||||||
|
The `NodeTransformer` will walk the AST and use the return value of the
|
||||||
|
visitor functions to replace or remove the old node. If the return
|
||||||
|
value of the visitor function is `None` the node will be removed
|
||||||
|
from the previous location otherwise it's replaced with the return
|
||||||
|
value. The return value may be the original node in which case no
|
||||||
|
replacement takes place.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def generic_visit(self, node, *args, **kwargs):
|
||||||
|
for field, old_value in node.iter_fields():
|
||||||
|
if isinstance(old_value, list):
|
||||||
|
new_values = []
|
||||||
|
for value in old_value:
|
||||||
|
if isinstance(value, Node):
|
||||||
|
value = self.visit(value, *args, **kwargs)
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
elif not isinstance(value, Node):
|
||||||
|
new_values.extend(value)
|
||||||
|
continue
|
||||||
|
new_values.append(value)
|
||||||
|
old_value[:] = new_values
|
||||||
|
elif isinstance(old_value, Node):
|
||||||
|
new_node = self.visit(old_value, *args, **kwargs)
|
||||||
|
if new_node is None:
|
||||||
|
delattr(node, field)
|
||||||
|
else:
|
||||||
|
setattr(node, field, new_node)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def visit_list(self, node, *args, **kwargs):
|
||||||
|
"""As transformers may return lists in some places this method
|
||||||
|
can be used to enforce a list as return value.
|
||||||
|
"""
|
||||||
|
rv = self.visit(node, *args, **kwargs)
|
||||||
|
if not isinstance(rv, list):
|
||||||
|
rv = [rv]
|
||||||
|
return rv
|
Loading…
Reference in a new issue