mirror of
https://gitee.com/openharmony/third_party_jinja2
synced 2024-11-27 17:30:54 +00:00
a clean restart
--HG-- branch : trunk rename : jinja/__init__.py => jinja2/__init__.py rename : jinja/_debugger.c => jinja2/_debugger.c rename : jinja/_native.py => jinja2/_native.py rename : jinja/_speedups.c => jinja2/_speedups.c rename : jinja/constants.py => jinja2/constants.py rename : jinja/contrib/__init__.py => jinja2/contrib/__init__.py rename : jinja/contrib/_djangosupport.py => jinja2/contrib/_djangosupport.py rename : jinja/contrib/djangosupport.py => jinja2/contrib/djangosupport.py rename : jinja/datastructure.py => jinja2/datastructure.py rename : jinja/defaults.py => jinja2/defaults.py rename : jinja/environment.py => jinja2/environment.py rename : jinja/exceptions.py => jinja2/exceptions.py rename : jinja/filters.py => jinja2/filters.py rename : jinja/lexer.py => jinja2/lexer.py rename : jinja/loaders.py => jinja2/loaders.py rename : jinja/nodes.py => jinja2/nodes.py rename : jinja/parser.py => jinja2/parser.py rename : jinja/tests.py => jinja2/tests.py rename : jinja/translators/__init__.py => jinja2/translators/__init__.py rename : jinja/translators/python.py => jinja2/translators/python.py rename : jinja/utils.py => jinja2/utils.py
This commit is contained in:
parent
e074cd2d47
commit
07bc684ccd
@ -1,708 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.datastructure
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Module that helds several data types used in the template engine.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja.exceptions import TemplateSyntaxError, TemplateRuntimeError
|
||||
|
||||
_missing = object()
|
||||
|
||||
|
||||
def contextcallable(f):
|
||||
"""
|
||||
Mark a function context callable.
|
||||
"""
|
||||
f.jinja_context_callable = True
|
||||
return f
|
||||
|
||||
|
||||
def unsafe(f):
|
||||
"""
|
||||
Mark function as unsafe.
|
||||
"""
|
||||
f.jinja_unsafe_call = True
|
||||
return f
|
||||
|
||||
|
||||
def make_undefined(implementation):
|
||||
"""
|
||||
Creates an undefined singleton based on a given implementation.
|
||||
It performs some tests that make sure the undefined type implements
|
||||
everything it should.
|
||||
"""
|
||||
self = object.__new__(implementation)
|
||||
self.__reduce__()
|
||||
return self
|
||||
|
||||
|
||||
class AbstractUndefinedType(object):
|
||||
"""
|
||||
Base class for any undefined type.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self):
|
||||
raise TypeError('cannot create %r instances' %
|
||||
self.__class__.__name__)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise AttributeError('%r object has no attribute %r' % (
|
||||
self.__class__.__name__,
|
||||
name
|
||||
))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self is not other
|
||||
|
||||
def __copy__(self):
|
||||
return self
|
||||
__deepcopy__ = __copy__
|
||||
|
||||
def __repr__(self):
|
||||
return 'Undefined'
|
||||
|
||||
def __reduce__(self):
|
||||
raise TypeError('undefined objects have to provide a __reduce__')
|
||||
|
||||
|
||||
class SilentUndefinedType(AbstractUndefinedType):
|
||||
"""
|
||||
An object that does not exist.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __add__(self, other):
|
||||
"""Any operator returns the operand."""
|
||||
return other
|
||||
__sub__ = __mul__ = __div__ = __rsub__ = __rmul__ = __div__ = __mod__ =\
|
||||
__radd__ = __rmod__ = __add__
|
||||
|
||||
def __getitem__(self, arg):
|
||||
"""Getting any item returns `Undefined`"""
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterating over `Undefined` returns an empty iterator."""
|
||||
if False:
|
||||
yield None
|
||||
|
||||
def __getattr__(self, arg):
|
||||
"""Getting any attribute returns `Undefined`"""
|
||||
return self
|
||||
|
||||
def __nonzero__(self):
|
||||
"""`Undefined` is considered boolean `False`"""
|
||||
return False
|
||||
|
||||
def __len__(self):
|
||||
"""`Undefined` is an empty sequence"""
|
||||
return 0
|
||||
|
||||
def __str__(self):
|
||||
"""The string representation is empty."""
|
||||
return ''
|
||||
|
||||
def __unicode__(self):
|
||||
"""The unicode representation is empty."""
|
||||
return u''
|
||||
|
||||
def __int__(self):
|
||||
"""Converting `Undefined` to an integer ends up in ``0``"""
|
||||
return 0
|
||||
|
||||
def __float__(self):
|
||||
"""Converting `Undefined` to an float ends up in ``0.0``"""
|
||||
return 0.0
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Calling `Undefined` returns `Undefined`"""
|
||||
return self
|
||||
|
||||
def __reduce__(self):
|
||||
"""Helper for pickle."""
|
||||
return 'SilentUndefined'
|
||||
|
||||
|
||||
class ComplainingUndefinedType(AbstractUndefinedType):
|
||||
"""
|
||||
An object that does not exist.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __len__(self):
|
||||
"""Getting the length raises error."""
|
||||
raise TemplateRuntimeError('Operated on undefined object')
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterating over `Undefined` raises an error."""
|
||||
raise TemplateRuntimeError('Iterated over undefined object')
|
||||
|
||||
def __nonzero__(self):
|
||||
"""`Undefined` is considered boolean `False`"""
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
"""The string representation raises an error."""
|
||||
raise TemplateRuntimeError('Undefined object rendered')
|
||||
|
||||
def __unicode__(self):
|
||||
"""The unicode representation raises an error."""
|
||||
self.__str__()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Calling `Undefined` returns `Undefined`"""
|
||||
raise TemplateRuntimeError('Undefined object called')
|
||||
|
||||
def __reduce__(self):
|
||||
"""Helper for pickle."""
|
||||
return 'ComplainingUndefined'
|
||||
|
||||
|
||||
#: the singleton instances for the undefined objects
|
||||
SilentUndefined = make_undefined(SilentUndefinedType)
|
||||
ComplainingUndefined = make_undefined(ComplainingUndefinedType)
|
||||
|
||||
#: jinja 1.0 compatibility
|
||||
Undefined = SilentUndefined
|
||||
UndefinedType = SilentUndefinedType
|
||||
|
||||
|
||||
class FakeTranslator(object):
|
||||
"""
|
||||
Default null translator.
|
||||
"""
|
||||
|
||||
def gettext(self, s):
|
||||
"""
|
||||
Translate a singular string.
|
||||
"""
|
||||
return s
|
||||
|
||||
def ngettext(self, s, p, n):
|
||||
"""
|
||||
Translate a plural string.
|
||||
"""
|
||||
if n == 1:
|
||||
return s
|
||||
return p
|
||||
|
||||
|
||||
class Deferred(object):
|
||||
"""
|
||||
Object marking an deferred value. Deferred objects are
|
||||
objects that are called first access in the context.
|
||||
"""
|
||||
|
||||
def __init__(self, factory):
|
||||
self.factory = factory
|
||||
|
||||
def __call__(self, context, name):
|
||||
return self.factory(context.environment, context, name)
|
||||
|
||||
|
||||
class Markup(unicode):
|
||||
"""
|
||||
Compatibility for Pylons and probably some other frameworks.
|
||||
|
||||
It's only used in Jinja environments with `auto_escape` set
|
||||
to true.
|
||||
"""
|
||||
|
||||
def __html__(self):
|
||||
return unicode(self)
|
||||
|
||||
|
||||
class TemplateData(Markup):
|
||||
"""
|
||||
Subclass of unicode to mark objects that are coming from the
|
||||
template. The autoescape filter can use that.
|
||||
"""
|
||||
|
||||
|
||||
# import these here because those modules import Deferred and Undefined
|
||||
# from this module.
|
||||
try:
|
||||
# try to use the c implementation of the base context if available
|
||||
from jinja._speedups import BaseContext
|
||||
except ImportError:
|
||||
# if there is no c implementation we go with a native python one
|
||||
from jinja._native import BaseContext
|
||||
|
||||
|
||||
class Context(BaseContext):
|
||||
"""
|
||||
Dict like object containing the variables for the template.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
environment = args[0]
|
||||
if not kwargs and len(args) == 2 and isinstance(args[1], dict):
|
||||
base = args[1]
|
||||
else:
|
||||
base = dict(*args[1:], **kwargs)
|
||||
super(Context, self).__init__(environment.undefined_singleton,
|
||||
environment.globals, base)
|
||||
self._translate_func = None
|
||||
self.cache = {}
|
||||
self.environment = environment
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Convert the context into a dict. This skips the globals.
|
||||
"""
|
||||
result = {}
|
||||
for layer in self.stack[1:]:
|
||||
for key, value in layer.iteritems():
|
||||
if key.startswith('::'):
|
||||
continue
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
def set_nonlocal(self, name, value):
|
||||
"""
|
||||
Set a value in an outer scope.
|
||||
"""
|
||||
for layer in self.stack[:0:-1]:
|
||||
if name in layer:
|
||||
layer[name] = value
|
||||
return
|
||||
self.initial[name] = value
|
||||
|
||||
def translate_func(self):
|
||||
"""
|
||||
The translation function for this context. It takes
|
||||
4 parameters. The singular string, the optional plural one,
|
||||
The name of the variable in the replacements dict and the
|
||||
replacements dict. This is only used by the i18n system
|
||||
internally the simplified version (just one argument) is
|
||||
available in the template for the user too.
|
||||
"""
|
||||
if self._translate_func is not None:
|
||||
return self._translate_func
|
||||
translator = self.environment.get_translator(self)
|
||||
gettext = translator.gettext
|
||||
ngettext = translator.ngettext
|
||||
def translate(s, p=None, n=None, r=None):
|
||||
if p is None:
|
||||
s = gettext(s)
|
||||
else:
|
||||
s = ngettext(s, p, r[n])
|
||||
# apply replacement substitution only if replacements
|
||||
# are given. This is the case for {% trans %}...{% endtrans %}
|
||||
# but for the "_()" syntax and a trans tag without a body.
|
||||
if r is not None:
|
||||
return s % r
|
||||
return s
|
||||
translate.__doc__ = Context.translate_func.__doc__
|
||||
self._translate_func = translate
|
||||
return translate
|
||||
translate_func = property(translate_func, doc=translate_func.__doc__)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
String representation of the context.
|
||||
"""
|
||||
return 'Context(%r)' % self.to_dict()
|
||||
|
||||
def __pretty__(self, p, cycle):
|
||||
if cycle:
|
||||
return p.text('Context({...})')
|
||||
p.begin_group(9, 'Context({')
|
||||
for idx, (key, value) in enumerate(self.to_dict().iteritems()):
|
||||
if idx:
|
||||
p.text(',')
|
||||
p.breakable()
|
||||
p.pretty(key)
|
||||
p.text(': ')
|
||||
p.pretty(value)
|
||||
p.end_group(9, '})')
|
||||
|
||||
|
||||
class LoopContext(object):
|
||||
"""
|
||||
Simple class that provides special loop variables.
|
||||
Used by `Environment.iterate`.
|
||||
"""
|
||||
|
||||
jinja_allowed_attributes = ['index', 'index0', 'length', 'parent',
|
||||
'even', 'odd', 'revindex0', 'revindex',
|
||||
'first', 'last']
|
||||
|
||||
def __init__(self, seq, parent, loop_function):
|
||||
self.loop_function = loop_function
|
||||
self.parent = parent
|
||||
self._stack = []
|
||||
if loop_function is None:
|
||||
self.push(seq)
|
||||
|
||||
def push(self, seq):
|
||||
"""
|
||||
Push a sequence to the loop stack. This is used by the
|
||||
recursive for loop.
|
||||
"""
|
||||
# iteration over None is catched, but we don't catch iteration
|
||||
# over undefined because that behavior is handled in the
|
||||
# undefined singleton
|
||||
if seq is None:
|
||||
seq = ()
|
||||
length = 0
|
||||
else:
|
||||
try:
|
||||
length = len(seq)
|
||||
except (AttributeError, TypeError):
|
||||
seq = list(seq)
|
||||
length = len(seq)
|
||||
self._stack.append({
|
||||
'index': -1,
|
||||
'seq': seq,
|
||||
'length': length
|
||||
})
|
||||
return self
|
||||
|
||||
def pop(self):
|
||||
"""Remove the last layer from the loop stack."""
|
||||
return self._stack.pop()
|
||||
|
||||
iterated = property(lambda s: s._stack[-1]['index'] > -1)
|
||||
index0 = property(lambda s: s._stack[-1]['index'])
|
||||
index = property(lambda s: s._stack[-1]['index'] + 1)
|
||||
revindex0 = property(lambda s: s._stack[-1]['length'] -
|
||||
s._stack[-1]['index'] - 1)
|
||||
revindex = property(lambda s: s._stack[-1]['length'] -
|
||||
s._stack[-1]['index'])
|
||||
length = property(lambda s: s._stack[-1]['length'])
|
||||
even = property(lambda s: s._stack[-1]['index'] % 2 == 1)
|
||||
odd = property(lambda s: s._stack[-1]['index'] % 2 == 0)
|
||||
first = property(lambda s: s._stack[-1]['index'] == 0)
|
||||
last = property(lambda s: s._stack[-1]['index'] ==
|
||||
s._stack[-1]['length'] - 1)
|
||||
|
||||
def __iter__(self):
|
||||
s = self._stack[-1]
|
||||
for idx, item in enumerate(s['seq']):
|
||||
s['index'] = idx
|
||||
yield item
|
||||
|
||||
def __len__(self):
|
||||
return self._stack[-1]['length']
|
||||
|
||||
def __call__(self, seq):
|
||||
if self.loop_function is not None:
|
||||
return self.loop_function(seq)
|
||||
raise TemplateRuntimeError('In order to make loops callable you have '
|
||||
'to define them with the "recursive" '
|
||||
'modifier.')
|
||||
|
||||
def __repr__(self):
|
||||
if self._stack:
|
||||
return '<LoopContext %d/%d%s>' % (
|
||||
self.index,
|
||||
self.length,
|
||||
self.loop_function is not None and ' recursive' or ''
|
||||
)
|
||||
return '<LoopContext (empty)>'
|
||||
|
||||
|
||||
class CycleContext(object):
|
||||
"""
|
||||
Helper class used for cycling.
|
||||
"""
|
||||
|
||||
def __init__(self, seq=None):
|
||||
self.pos = -1
|
||||
# bind the correct helper function based on the constructor signature
|
||||
if seq is not None:
|
||||
self.seq = seq
|
||||
self.length = len(seq)
|
||||
self.cycle = self.cycle_static
|
||||
else:
|
||||
self.cycle = self.cycle_dynamic
|
||||
|
||||
def cycle_static(self):
|
||||
"""Helper function for static cycling."""
|
||||
self.pos = (self.pos + 1) % self.length
|
||||
return self.seq[self.pos]
|
||||
|
||||
def cycle_dynamic(self, seq):
|
||||
"""Helper function for dynamic cycling."""
|
||||
self.pos = pos = (self.pos + 1) % len(seq)
|
||||
return seq[pos]
|
||||
|
||||
|
||||
class SuperBlock(object):
|
||||
"""
|
||||
Helper class for ``{{ super() }}``.
|
||||
"""
|
||||
jinja_allowed_attributes = ['name']
|
||||
|
||||
def __init__(self, name, blocks, level, context):
|
||||
self.name = name
|
||||
self.context = context
|
||||
if name in blocks:
|
||||
self.stack = blocks[name]
|
||||
self.level = level
|
||||
else:
|
||||
self.stack = None
|
||||
|
||||
def __call__(self, offset=1):
|
||||
if self.stack is not None:
|
||||
level = self.level + (offset - 1)
|
||||
if level < len(self.stack):
|
||||
return self.stack[level](self.context)
|
||||
raise TemplateRuntimeError('no super block for %r' % self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<SuperBlock %r>' % self.name
|
||||
|
||||
|
||||
class StateTest(object):
|
||||
"""
|
||||
Wrapper class for basic lambdas in order to simplify
|
||||
debugging in the parser. It also provides static helper
|
||||
functions that replace some lambda expressions
|
||||
"""
|
||||
|
||||
def __init__(self, func, msg):
|
||||
self.func = func
|
||||
self.msg = msg
|
||||
|
||||
def __call__(self, token):
|
||||
return self.func(token)
|
||||
|
||||
def expect_token(*types, **kw):
|
||||
"""Scans until one of the given tokens is found."""
|
||||
msg = kw.pop('msg', None)
|
||||
if kw:
|
||||
raise TypeError('unexpected keyword argument %r' % iter(kw).next())
|
||||
if len(types) == 1:
|
||||
if msg is None:
|
||||
msg = "expected '%s'" % types[0]
|
||||
return StateTest(lambda t: t.type == types[0], msg)
|
||||
if msg is None:
|
||||
msg = 'expected one of %s' % ', '.join(["'%s'" % type
|
||||
for type in types])
|
||||
return StateTest(lambda t: t.type in types, msg)
|
||||
expect_token = staticmethod(expect_token)
|
||||
|
||||
|
||||
class Token(object):
|
||||
"""
|
||||
Token class.
|
||||
"""
|
||||
__slots__ = ('lineno', 'type', 'value')
|
||||
|
||||
def __init__(self, lineno, type, value):
|
||||
self.lineno = lineno
|
||||
self.type = intern(str(type))
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
from jinja.lexer import keywords, reverse_operators
|
||||
if self.type in keywords:
|
||||
return self.type
|
||||
elif self.type in reverse_operators:
|
||||
return reverse_operators[self.type]
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
return 'Token(%r, %r, %r)' % (
|
||||
self.lineno,
|
||||
self.type,
|
||||
self.value
|
||||
)
|
||||
|
||||
|
||||
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 == 'eof':
|
||||
self._stream.close()
|
||||
raise StopIteration()
|
||||
self._stream.next()
|
||||
return token
|
||||
|
||||
|
||||
class TokenStream(object):
|
||||
"""
|
||||
A token stream wraps a generator and supports pushing tokens back.
|
||||
It also provides some functions to expect tokens and similar stuff.
|
||||
|
||||
Important note: Do never push more than one token back to the
|
||||
stream. Although the stream object won't stop you
|
||||
from doing so, the behavior is undefined. Multiple
|
||||
pushed tokens are only used internally!
|
||||
"""
|
||||
|
||||
def __init__(self, generator, filename):
|
||||
self._next = generator.next
|
||||
self._pushed = []
|
||||
self.current = Token(1, 'initial', '')
|
||||
self.filename = filename
|
||||
self.next()
|
||||
|
||||
def __iter__(self):
|
||||
return TokenStreamIterator(self)
|
||||
|
||||
def bound(self):
|
||||
"""Return True if the token stream is bound to a parser."""
|
||||
return self.parser is not None
|
||||
bound = property(bound, doc=bound.__doc__)
|
||||
|
||||
def lineno(self):
|
||||
"""The current line number."""
|
||||
return self.current.lineno
|
||||
lineno = property(lineno, doc=lineno.__doc__)
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Are we at the end of the tokenstream?"""
|
||||
return bool(self._pushed) or self.current.type != 'eof'
|
||||
|
||||
eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
|
||||
|
||||
def look(self):
|
||||
"""See what's the next token."""
|
||||
if self._pushed:
|
||||
return self._pushed[-1]
|
||||
old_token = self.current
|
||||
self.next()
|
||||
new_token = self.current
|
||||
self.current = old_token
|
||||
self.push(new_token)
|
||||
return new_token
|
||||
|
||||
def push(self, token):
|
||||
"""Push a token back to the stream."""
|
||||
self._pushed.append(token)
|
||||
|
||||
def skip(self, n):
|
||||
"""Got n tokens ahead."""
|
||||
for x in xrange(n):
|
||||
self.next()
|
||||
|
||||
def shift(self, token):
|
||||
"""
|
||||
Push one token into the stream.
|
||||
"""
|
||||
old_current = self.current
|
||||
self.next()
|
||||
self.push(self.current)
|
||||
self.push(old_current)
|
||||
self.push(token)
|
||||
self.next()
|
||||
|
||||
def next(self):
|
||||
"""Go one token ahead."""
|
||||
if self._pushed:
|
||||
self.current = self._pushed.pop()
|
||||
elif self.current.type != 'eof':
|
||||
try:
|
||||
self.current = self._next()
|
||||
except StopIteration:
|
||||
self.close()
|
||||
|
||||
def read_whitespace(self):
|
||||
"""Read all the whitespace, up to the next tag."""
|
||||
lineno = self.current.lineno
|
||||
buf = []
|
||||
while self.current.type == 'data' and not \
|
||||
self.current.value.strip():
|
||||
buf.append(self.current.value)
|
||||
self.next()
|
||||
if buf:
|
||||
return Token(lineno, 'data', u''.join(buf))
|
||||
|
||||
def close(self):
|
||||
"""Close the stream."""
|
||||
self.current = Token(self.current.lineno, 'eof', '')
|
||||
self._next = None
|
||||
|
||||
def expect(self, token_type, token_value=_missing):
|
||||
"""Expect a given token type and return it"""
|
||||
if self.current.type != token_type:
|
||||
raise TemplateSyntaxError("expected token %r, got %r" %
|
||||
(token_type, self.current.type),
|
||||
self.current.lineno,
|
||||
self.filename)
|
||||
elif token_value is not _missing and \
|
||||
self.current.value != token_value:
|
||||
raise TemplateSyntaxError("expected %r, got %r" %
|
||||
(token_value, self.current.value),
|
||||
self.current.lineno,
|
||||
self.filename)
|
||||
try:
|
||||
return self.current
|
||||
finally:
|
||||
self.next()
|
||||
|
||||
|
||||
class TemplateStream(object):
|
||||
"""
|
||||
Wraps a genererator for outputing template streams.
|
||||
"""
|
||||
|
||||
def __init__(self, gen):
|
||||
self._gen = gen
|
||||
self._next = gen.next
|
||||
self.buffered = False
|
||||
|
||||
def disable_buffering(self):
|
||||
"""
|
||||
Disable the output buffering.
|
||||
"""
|
||||
self._next = self._gen.next
|
||||
self.buffered = False
|
||||
|
||||
def enable_buffering(self, size=5):
|
||||
"""
|
||||
Enable buffering. Buffer `size` items before
|
||||
yielding them.
|
||||
"""
|
||||
if size <= 1:
|
||||
raise ValueError('buffer size too small')
|
||||
self.buffered = True
|
||||
|
||||
def buffering_next():
|
||||
buf = []
|
||||
c_size = 0
|
||||
push = buf.append
|
||||
next = self._gen.next
|
||||
|
||||
try:
|
||||
while True:
|
||||
item = next()
|
||||
if item:
|
||||
push(item)
|
||||
c_size += 1
|
||||
if c_size >= size:
|
||||
raise StopIteration()
|
||||
except StopIteration:
|
||||
if not c_size:
|
||||
raise
|
||||
return u''.join(buf)
|
||||
|
||||
self._next = buffering_next
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
return self._next()
|
@ -1,213 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.debugger
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements helper function Jinja uses to give the users a
|
||||
possibility to develop Jinja templates like they would debug python code.
|
||||
It seamlessly integreates into the python traceback system, in fact it
|
||||
just modifies the trackback stack so that the line numbers are correct
|
||||
and the frame information are bound to the context and not the frame of
|
||||
the template evaluation loop.
|
||||
|
||||
To achive this it raises the exception it cought before in an isolated
|
||||
namespace at a given line. The locals namespace is set to the current
|
||||
template context.
|
||||
|
||||
The traceback generated by raising that exception is then either returned
|
||||
or linked with the former traceback if the `jinja._debugger` module is
|
||||
available. Because it's not possible to modify traceback objects from the
|
||||
python space this module is needed for this process.
|
||||
|
||||
If it's not available it just ignores the other frames. Because this can
|
||||
lead to actually harder to debug code there is a setting on the jinja
|
||||
environment to disable the debugging system.
|
||||
|
||||
The isolated namespace which is used to raise the exception also contains
|
||||
a `__loader__` name that helds a reference to a PEP 302 compatible loader.
|
||||
Because there are currently some traceback systems (such as the paste
|
||||
evalexception debugger) that do not provide the frame globals when
|
||||
retrieving the source from the linecache module, Jinja injects the source
|
||||
to the linecache module itself and changes the filename to a URL style
|
||||
"virtual filename" so that Jinja doesn't acidentally override other files
|
||||
in the linecache.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from random import randrange
|
||||
|
||||
# if we have extended debugger support we should really use it
|
||||
try:
|
||||
from jinja._debugger import *
|
||||
has_extended_debugger = True
|
||||
except ImportError:
|
||||
has_extended_debugger = False
|
||||
|
||||
# we need the RUNTIME_EXCEPTION_OFFSET to skip the not used frames
|
||||
from jinja.utils import reversed, RUNTIME_EXCEPTION_OFFSET
|
||||
|
||||
|
||||
def fake_template_exception(exc_type, exc_value, tb, filename, lineno,
|
||||
source, context_or_env, tb_back=None):
|
||||
"""
|
||||
Raise an exception "in a template". Return a traceback
|
||||
object. This is used for runtime debugging, not compile time.
|
||||
"""
|
||||
# some traceback systems allow to skip frames
|
||||
__traceback_hide__ = True
|
||||
|
||||
# create the namespace which will be the local namespace
|
||||
# of the new frame then. Some debuggers show local variables
|
||||
# so we better inject the context and not the evaluation loop context.
|
||||
from jinja.datastructure import Context
|
||||
if isinstance(context_or_env, Context):
|
||||
env = context_or_env.environment
|
||||
namespace = context_or_env.to_dict()
|
||||
else:
|
||||
env = context_or_env
|
||||
namespace = {}
|
||||
|
||||
# no unicode for filenames
|
||||
if isinstance(filename, unicode):
|
||||
filename = filename.encode('utf-8')
|
||||
|
||||
# generate an jinja unique filename used so that linecache
|
||||
# gets data that doesn't interfere with other modules
|
||||
if filename is None:
|
||||
vfilename = 'jinja://~%d' % randrange(0, 10000)
|
||||
filename = '<string>'
|
||||
else:
|
||||
vfilename = 'jinja://%s' % filename
|
||||
|
||||
# now create the used loaded and update the linecache
|
||||
loader = TracebackLoader(env, source, filename)
|
||||
loader.update_linecache(vfilename)
|
||||
globals = {
|
||||
'__name__': vfilename,
|
||||
'__file__': vfilename,
|
||||
'__loader__': loader
|
||||
}
|
||||
|
||||
# use the simple debugger to reraise the exception in the
|
||||
# line where the error originally occoured
|
||||
globals['__exception_to_raise__'] = (exc_type, exc_value)
|
||||
offset = '\n' * (lineno - 1)
|
||||
code = compile(offset + 'raise __exception_to_raise__[0], '
|
||||
'__exception_to_raise__[1]',
|
||||
vfilename or '<template>', 'exec')
|
||||
try:
|
||||
exec code in globals, namespace
|
||||
except:
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
# if we have an extended debugger we set the tb_next flag so that
|
||||
# we don't loose the higher stack items.
|
||||
if has_extended_debugger:
|
||||
if tb_back is not None:
|
||||
tb_set_next(tb_back, exc_info[2])
|
||||
if tb is not None:
|
||||
tb_set_next(exc_info[2].tb_next, tb.tb_next)
|
||||
|
||||
# otherwise just return the exc_info from the simple debugger
|
||||
return exc_info
|
||||
|
||||
|
||||
def translate_exception(template, context, exc_type, exc_value, tb):
|
||||
"""
|
||||
Translate an exception and return the new traceback.
|
||||
"""
|
||||
# depending on the python version we have to skip some frames to
|
||||
# step to get the frame of the current template. The frames before
|
||||
# are the toolchain used to render that thing.
|
||||
for x in xrange(RUNTIME_EXCEPTION_OFFSET):
|
||||
tb = tb.tb_next
|
||||
|
||||
result_tb = prev_tb = None
|
||||
initial_tb = tb
|
||||
|
||||
# translate all the jinja frames in this traceback
|
||||
while tb is not None:
|
||||
if tb.tb_frame.f_globals.get('__jinja_template__'):
|
||||
debug_info = tb.tb_frame.f_globals['debug_info']
|
||||
|
||||
# the next thing we do is matching the current error line against the
|
||||
# debugging table to get the correct source line. If we can't find the
|
||||
# filename and line number we return the traceback object unaltered.
|
||||
error_line = tb.tb_lineno
|
||||
for code_line, tmpl_filename, tmpl_line in reversed(debug_info):
|
||||
if code_line <= error_line:
|
||||
source = tb.tb_frame.f_globals['template_source']
|
||||
tb = fake_template_exception(exc_type, exc_value, tb,
|
||||
tmpl_filename, tmpl_line,
|
||||
source, context, prev_tb)[-1]
|
||||
break
|
||||
if result_tb is None:
|
||||
result_tb = tb
|
||||
prev_tb = tb
|
||||
tb = tb.tb_next
|
||||
|
||||
# under some conditions we cannot translate any frame. in that
|
||||
# situation just return the original traceback.
|
||||
return (exc_type, exc_value, result_tb or intial_tb)
|
||||
|
||||
|
||||
def raise_syntax_error(exception, env, source=None):
|
||||
"""
|
||||
This method raises an exception that includes more debugging
|
||||
informations so that debugging works better. Unlike
|
||||
`translate_exception` this method raises the exception with
|
||||
the traceback.
|
||||
"""
|
||||
exc_info = fake_template_exception(exception, None, None,
|
||||
exception.filename,
|
||||
exception.lineno, source, env)
|
||||
raise exc_info[0], exc_info[1], exc_info[2]
|
||||
|
||||
|
||||
class TracebackLoader(object):
|
||||
"""
|
||||
Fake importer that just returns the source of a template. It's just used
|
||||
by Jinja interally and you shouldn't use it on your own.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, source, filename):
|
||||
self.loader = environment.loader
|
||||
self.source = source
|
||||
self.filename = filename
|
||||
|
||||
def update_linecache(self, virtual_filename):
|
||||
"""
|
||||
Hacky way to let traceback systems know about the
|
||||
Jinja template sourcecode. Very hackish indeed.
|
||||
"""
|
||||
# check for linecache, not every implementation of python
|
||||
# might have such an module (this check is pretty senseless
|
||||
# because we depend on cpython anway)
|
||||
try:
|
||||
from linecache import cache
|
||||
except ImportError:
|
||||
return
|
||||
data = self.get_source(None)
|
||||
cache[virtual_filename] = (
|
||||
len(data),
|
||||
None,
|
||||
data.splitlines(True),
|
||||
virtual_filename
|
||||
)
|
||||
|
||||
def get_source(self, impname):
|
||||
"""Return the source as bytestring."""
|
||||
source = ''
|
||||
if self.source is not None:
|
||||
source = self.source
|
||||
elif self.loader is not None:
|
||||
try:
|
||||
source = self.loader.get_source(self.filename)
|
||||
except TemplateNotFound:
|
||||
pass
|
||||
if isinstance(source, unicode):
|
||||
source = source.encode('utf-8')
|
||||
return source
|
@ -1,404 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.environment
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides a class that holds runtime and parsing time options.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja.lexer import Lexer
|
||||
from jinja.parser import Parser
|
||||
from jinja.loaders import LoaderWrapper
|
||||
from jinja.datastructure import SilentUndefined, Markup, Context, FakeTranslator
|
||||
from jinja.translators.python import PythonTranslator
|
||||
from jinja.utils import collect_translations, get_attribute
|
||||
from jinja.exceptions import FilterNotFound, TestNotFound, \
|
||||
SecurityException, TemplateSyntaxError
|
||||
from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
|
||||
|
||||
|
||||
__all__ = ['Environment']
|
||||
|
||||
|
||||
#: minor speedup
|
||||
_getattr = getattr
|
||||
|
||||
|
||||
class Environment(object):
|
||||
"""
|
||||
The Jinja environment.
|
||||
|
||||
The core component of Jinja is the `Environment`. It contains
|
||||
important shared variables like configuration, filters, tests,
|
||||
globals and others.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
block_start_string='{%',
|
||||
block_end_string='%}',
|
||||
variable_start_string='{{',
|
||||
variable_end_string='}}',
|
||||
comment_start_string='{#',
|
||||
comment_end_string='#}',
|
||||
trim_blocks=False,
|
||||
auto_escape=False,
|
||||
default_filters=None,
|
||||
template_charset='utf-8',
|
||||
charset='utf-8',
|
||||
namespace=None,
|
||||
loader=None,
|
||||
filters=None,
|
||||
tests=None,
|
||||
context_class=Context,
|
||||
undefined_singleton=SilentUndefined,
|
||||
disable_regexps=False,
|
||||
friendly_traceback=True,
|
||||
translator_factory=None,
|
||||
template_translator=PythonTranslator):
|
||||
"""
|
||||
Here the possible initialization parameters:
|
||||
|
||||
========================= ============================================
|
||||
`block_start_string` * the string marking the begin of a block.
|
||||
this defaults to ``'{%'``.
|
||||
`block_end_string` * the string marking the end of a block.
|
||||
defaults to ``'%}'``.
|
||||
`variable_start_string` * the string marking the begin of a print
|
||||
statement. defaults to ``'{{'``.
|
||||
`comment_start_string` * the string marking the begin of a
|
||||
comment. defaults to ``'{#'``.
|
||||
`comment_end_string` * the string marking the end of a comment.
|
||||
defaults to ``'#}'``.
|
||||
`trim_blocks` * If this is set to ``True`` the first newline
|
||||
after a block is removed (block, not
|
||||
variable tag!). Defaults to ``False``.
|
||||
`auto_escape` If this is set to ``True`` Jinja will
|
||||
automatically escape all variables using xml
|
||||
escaping methods. If you don't want to
|
||||
escape a string you have to wrap it in a
|
||||
``Markup`` object from the
|
||||
``jinja.datastructure`` module. If
|
||||
`auto_escape` is ``True`` there will be also
|
||||
a ``Markup`` object in the template
|
||||
namespace to define partial html fragments.
|
||||
Note that we do not recommend this feature.
|
||||
`default_filters` list of tuples in the form (``filter_name``,
|
||||
``arguments``) where ``filter_name`` is the
|
||||
name of a registered filter and
|
||||
``arguments`` a tuple with the filter
|
||||
arguments. The filters specified here will
|
||||
always be applied when printing data to the
|
||||
template. *new in Jinja 1.1*
|
||||
`template_charset` The charset of the templates. Defaults
|
||||
to ``'utf-8'``.
|
||||
`charset` Charset of all string input data. Defaults
|
||||
to ``'utf-8'``.
|
||||
`namespace` Global namespace for all templates.
|
||||
`loader` Specify a template loader.
|
||||
`filters` dict of filters or the default filters if
|
||||
not defined.
|
||||
`tests` dict of tests of the default tests if not
|
||||
defined.
|
||||
`context_class` the context class this template should use.
|
||||
See the `Context` documentation for more
|
||||
details.
|
||||
`undefined_singleton` The singleton value that is used for missing
|
||||
variables. *new in Jinja 1.1*
|
||||
`disable_regexps` Disable support for regular expresssions.
|
||||
`friendly_traceback` Set this to `False` to disable the developer
|
||||
friendly traceback rewriting. Whenever an
|
||||
runtime or syntax error occours jinja will
|
||||
try to make a developer friendly traceback
|
||||
that shows the error in the template line.
|
||||
This however can be annoying when debugging
|
||||
broken functions that are called from the
|
||||
template. *new in Jinja 1.1*
|
||||
`translator_factory` A callback function that is called with
|
||||
the context as first argument to get the
|
||||
translator for the current instance.
|
||||
*new in Jinja 1.2*
|
||||
`template_translator` An class that defines a static method called
|
||||
process which can be used to process the
|
||||
template's AST into a compiled python module.
|
||||
*new in Jinja 1.2*
|
||||
========================= ============================================
|
||||
|
||||
All of these variables except those marked with a star (*) are
|
||||
modifiable after environment initialization.
|
||||
"""
|
||||
|
||||
# lexer / parser information
|
||||
self.block_start_string = block_start_string
|
||||
self.block_end_string = block_end_string
|
||||
self.variable_start_string = variable_start_string
|
||||
self.variable_end_string = variable_end_string
|
||||
self.comment_start_string = comment_start_string
|
||||
self.comment_end_string = comment_end_string
|
||||
self.trim_blocks = trim_blocks
|
||||
|
||||
# other stuff
|
||||
self.template_charset = template_charset
|
||||
self.charset = charset
|
||||
self.loader = loader
|
||||
if filters is None:
|
||||
filters = DEFAULT_FILTERS.copy()
|
||||
self.filters = filters
|
||||
if tests is None:
|
||||
tests = DEFAULT_TESTS.copy()
|
||||
self.tests = tests
|
||||
self.default_filters = default_filters or []
|
||||
self.context_class = context_class
|
||||
self.undefined_singleton = undefined_singleton
|
||||
self.disable_regexps = disable_regexps
|
||||
self.friendly_traceback = friendly_traceback
|
||||
|
||||
# global namespace
|
||||
if namespace is None:
|
||||
namespace = DEFAULT_NAMESPACE.copy()
|
||||
self.globals = namespace
|
||||
|
||||
# jinja 1.0 compatibility
|
||||
if auto_escape:
|
||||
self.default_filters.append(('escape', (True,)))
|
||||
self.globals['Markup'] = Markup
|
||||
|
||||
# and here the translator factory
|
||||
self.translator_factory = translator_factory
|
||||
|
||||
# and here the AST translator
|
||||
self.template_translator = template_translator
|
||||
|
||||
# create lexer
|
||||
self.lexer = Lexer(self)
|
||||
|
||||
def loader(self, value):
|
||||
"""
|
||||
Get or set the template loader.
|
||||
"""
|
||||
self._loader = LoaderWrapper(self, value)
|
||||
loader = property(lambda s: s._loader, loader, doc=loader.__doc__)
|
||||
|
||||
def parse(self, source, filename=None):
|
||||
"""
|
||||
Parse the sourcecode and return the abstract syntax tree. This tree
|
||||
of nodes is used by the `translators`_ to convert the template into
|
||||
executable source- or bytecode.
|
||||
|
||||
.. _translators: translators.txt
|
||||
"""
|
||||
parser = Parser(self, source, filename)
|
||||
return parser.parse()
|
||||
|
||||
def lex(self, source, filename=None):
|
||||
"""
|
||||
Lex the given sourcecode and return a generator that yields tokens.
|
||||
The stream returned is not usable for Jinja but can be used if
|
||||
Jinja templates should be processed by other tools (for example
|
||||
syntax highlighting etc)
|
||||
|
||||
The tuples are returned in the form ``(lineno, token, value)``.
|
||||
"""
|
||||
return self.lexer.tokeniter(source, filename)
|
||||
|
||||
def from_string(self, source):
|
||||
"""
|
||||
Load and parse a template source and translate it into eval-able
|
||||
Python code. This code is wrapped within a `Template` class that
|
||||
allows you to render it.
|
||||
"""
|
||||
from jinja.translators.python import PythonTranslator
|
||||
try:
|
||||
rv = PythonTranslator.process(self, Parser(self, source).parse(),
|
||||
source)
|
||||
except TemplateSyntaxError, e:
|
||||
# on syntax errors rewrite the traceback if wanted
|
||||
if not self.friendly_traceback:
|
||||
raise
|
||||
from jinja.debugger import raise_syntax_error
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
raise_syntax_error(e, self, source)
|
||||
else:
|
||||
return rv
|
||||
|
||||
def get_template(self, filename):
|
||||
"""
|
||||
Load a template from a loader. If the template does not exist, you
|
||||
will get a `TemplateNotFound` exception.
|
||||
"""
|
||||
return self._loader.load(filename, translator=self.template_translator)
|
||||
|
||||
def to_unicode(self, value):
|
||||
"""
|
||||
Convert a value to unicode with the rules defined on the environment.
|
||||
"""
|
||||
# undefined and None expand to ""
|
||||
if value in (None, self.undefined_singleton):
|
||||
return u''
|
||||
# things that are already unicode can pass. As long as nobody
|
||||
# does ugly things with the class it works for jinja too
|
||||
elif isinstance(value, unicode):
|
||||
return value
|
||||
# otherwise try to use __unicode__ or decode __str__
|
||||
try:
|
||||
return unicode(value)
|
||||
except UnicodeError:
|
||||
return str(value).decode(self.charset, 'ignore')
|
||||
|
||||
def get_translator(self, context):
|
||||
"""
|
||||
Return the translator for i18n.
|
||||
|
||||
A translator is an object that provides the two functions
|
||||
``gettext(string)`` and ``ngettext(singular, plural, n)``. Note
|
||||
that both of them have to return unicode!
|
||||
"""
|
||||
if self.translator_factory is not None:
|
||||
return self.translator_factory(context)
|
||||
return FakeTranslator()
|
||||
|
||||
def get_translations(self, name):
|
||||
"""
|
||||
Load template `name` and return all translatable strings (note that
|
||||
that it really just returns the strings form this template, not from
|
||||
the parent or any included templates!)
|
||||
"""
|
||||
return collect_translations(self.loader.parse(name))
|
||||
|
||||
def get_translations_for_string(self, string):
|
||||
"""
|
||||
Like `get_translations`, but the translations are loaded from a
|
||||
normal string that represents the template.
|
||||
"""
|
||||
return collect_translations(self.parse(string))
|
||||
|
||||
def apply_filters(self, value, context, filters):
|
||||
"""
|
||||
Apply a list of filters on the variable.
|
||||
"""
|
||||
# some traceback systems allow to skip frames. but allow
|
||||
# disabling that via -O to not make things slow
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
|
||||
cache = context.cache
|
||||
for key in filters:
|
||||
if key in cache:
|
||||
func = cache[key]
|
||||
else:
|
||||
filtername, args = key
|
||||
if filtername not in self.filters:
|
||||
raise FilterNotFound(filtername)
|
||||
cache[key] = func = self.filters[filtername](*args)
|
||||
value = func(self, context, value)
|
||||
return value
|
||||
|
||||
def perform_test(self, context, testname, args, value):
|
||||
"""
|
||||
Perform a test on a variable.
|
||||
"""
|
||||
# some traceback systems allow to skip frames. but allow
|
||||
# disabling that via -O to not make things slow
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
|
||||
key = (testname, args)
|
||||
if key in context.cache:
|
||||
func = context.cache[key]
|
||||
else:
|
||||
if testname not in self.tests:
|
||||
raise TestNotFound(testname)
|
||||
context.cache[key] = func = self.tests[testname](*args)
|
||||
return not not func(self, context, value)
|
||||
|
||||
def get_attribute(self, obj, name):
|
||||
"""
|
||||
Get one attribute from an object.
|
||||
"""
|
||||
# some traceback systems allow to skip frames. but allow
|
||||
# disabling that via -O to not make things slow
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
|
||||
try:
|
||||
return obj[name]
|
||||
except (TypeError, KeyError, IndexError, AttributeError):
|
||||
try:
|
||||
return get_attribute(obj, name)
|
||||
except (AttributeError, SecurityException):
|
||||
pass
|
||||
if obj is self.undefined_singleton:
|
||||
return _getattr(obj, name)
|
||||
return self.undefined_singleton
|
||||
|
||||
def get_attributes(self, obj, attributes):
|
||||
"""
|
||||
Get some attributes from an object. If attributes is an
|
||||
empty sequence the object is returned as it.
|
||||
"""
|
||||
get = self.get_attribute
|
||||
for name in attributes:
|
||||
obj = get(obj, name)
|
||||
return obj
|
||||
|
||||
def call_function(self, f, context, args, kwargs, dyn_args, dyn_kwargs):
|
||||
"""
|
||||
Function call helper. Called for all functions that are passed
|
||||
any arguments.
|
||||
"""
|
||||
# some traceback systems allow to skip frames. but allow
|
||||
# disabling that via -O to not make things slow
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
|
||||
if dyn_args is not None:
|
||||
args += tuple(dyn_args)
|
||||
if dyn_kwargs is not None:
|
||||
kwargs.update(dyn_kwargs)
|
||||
if _getattr(f, 'jinja_unsafe_call', False) or \
|
||||
_getattr(f, 'alters_data', False):
|
||||
return self.undefined_singleton
|
||||
if _getattr(f, 'jinja_context_callable', False):
|
||||
args = (self, context) + args
|
||||
return f(*args, **kwargs)
|
||||
|
||||
def call_function_simple(self, f, context):
|
||||
"""
|
||||
Function call without arguments. Because of the smaller signature and
|
||||
fewer logic here we have a bit of redundant code.
|
||||
"""
|
||||
# some traceback systems allow to skip frames. but allow
|
||||
# disabling that via -O to not make things slow
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
|
||||
if _getattr(f, 'jinja_unsafe_call', False) or \
|
||||
_getattr(f, 'alters_data', False):
|
||||
return self.undefined_singleton
|
||||
if _getattr(f, 'jinja_context_callable', False):
|
||||
return f(self, context)
|
||||
return f()
|
||||
|
||||
def finish_var(self, value, ctx):
|
||||
"""
|
||||
As long as no write_var function is passed to the template
|
||||
evaluator the source generated by the python translator will
|
||||
call this function for all variables.
|
||||
"""
|
||||
# some traceback systems allow to skip frames. but allow
|
||||
# disabling that via -O to not make things slow
|
||||
if __debug__:
|
||||
__traceback_hide__ = True
|
||||
|
||||
if value is None:
|
||||
return u''
|
||||
elif value is self.undefined_singleton:
|
||||
return unicode(value)
|
||||
elif _getattr(value, 'jinja_no_finalization', False):
|
||||
return value
|
||||
val = self.to_unicode(value)
|
||||
if self.default_filters:
|
||||
val = self.apply_filters(val, ctx, self.default_filters)
|
||||
return val
|
@ -1,91 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.exceptions
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Jinja exceptions.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
class TemplateError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class SecurityException(TemplateError):
|
||||
"""
|
||||
Raise if the template designer tried to do something dangerous.
|
||||
"""
|
||||
|
||||
|
||||
class FilterNotFound(KeyError, TemplateError):
|
||||
"""
|
||||
Raised if a filter does not exist.
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
KeyError.__init__(self, message)
|
||||
|
||||
|
||||
class FilterArgumentError(TypeError, TemplateError):
|
||||
"""
|
||||
An argument passed to the filter was invalid.
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
TypeError.__init__(self, message)
|
||||
|
||||
|
||||
class TestNotFound(KeyError, TemplateError):
|
||||
"""
|
||||
Raised if a test does not exist.
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
KeyError.__init__(self, message)
|
||||
|
||||
|
||||
class TestArgumentError(TypeError, TemplateError):
|
||||
"""
|
||||
An argument passed to a test function was invalid.
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
TypeError.__init__(self, message)
|
||||
|
||||
|
||||
class TemplateNotFound(IOError, LookupError, TemplateError):
|
||||
"""
|
||||
Raised if a template does not exist.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
IOError.__init__(self, name)
|
||||
self.name = name
|
||||
|
||||
|
||||
class TemplateSyntaxError(SyntaxError, TemplateError):
|
||||
"""
|
||||
Raised to tell the user that there is a problem with the template.
|
||||
"""
|
||||
|
||||
def __init__(self, message, lineno, filename):
|
||||
SyntaxError.__init__(self, message)
|
||||
self.lineno = lineno
|
||||
self.filename = filename
|
||||
|
||||
|
||||
class TemplateRuntimeError(TemplateError):
|
||||
"""
|
||||
Raised by the template engine if a tag encountered an error when
|
||||
rendering.
|
||||
"""
|
||||
|
||||
|
||||
class TemplateIncludeError(TemplateError):
|
||||
"""
|
||||
Raised by the `ControlledLoader` if recursive includes where
|
||||
detected.
|
||||
"""
|
825
jinja/loaders.py
825
jinja/loaders.py
@ -1,825 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.loaders
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Jinja loader classes.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher, Bryan McLemore.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import codecs
|
||||
try:
|
||||
from hashlib import sha1
|
||||
except ImportError:
|
||||
from sha import new as sha1
|
||||
import time
|
||||
from os import path
|
||||
from threading import Lock
|
||||
from jinja.parser import Parser
|
||||
from jinja.translators.python import PythonTranslator, Template
|
||||
from jinja.exceptions import TemplateNotFound, TemplateSyntaxError, \
|
||||
TemplateIncludeError
|
||||
from jinja.utils import CacheDict
|
||||
|
||||
|
||||
#: when updating this, update the listing in the jinja package too
|
||||
__all__ = ['FileSystemLoader', 'PackageLoader', 'DictLoader', 'ChoiceLoader',
|
||||
'FunctionLoader', 'MemcachedFileSystemLoader']
|
||||
|
||||
|
||||
def get_template_filename(searchpath, name):
|
||||
"""
|
||||
Return the filesystem filename wanted.
|
||||
"""
|
||||
for sep in path.sep, path.altsep:
|
||||
if sep and sep in name:
|
||||
name = name.replace(sep, '/')
|
||||
return path.join(searchpath, *[p for p in name.split('/')
|
||||
if p and p[0] != '.'])
|
||||
|
||||
|
||||
def get_cachename(cachepath, name, salt=None):
|
||||
"""
|
||||
Return the filename for a cached file.
|
||||
"""
|
||||
return path.join(cachepath, 'jinja_%s.cache' %
|
||||
sha1('jinja(%s|%s)tmpl' %
|
||||
(name, salt or '')).hexdigest())
|
||||
|
||||
|
||||
def _loader_missing(*args, **kwargs):
|
||||
"""Helper function for `LoaderWrapper`."""
|
||||
raise RuntimeError('no loader defined')
|
||||
|
||||
|
||||
class LoaderWrapper(object):
|
||||
"""
|
||||
Wraps a loader so that it's bound to an environment.
|
||||
Also handles template syntax errors.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, loader):
|
||||
self.environment = environment
|
||||
self.loader = loader
|
||||
if self.loader is None:
|
||||
self.get_source = self.parse = self.load = _loader_missing
|
||||
self.available = False
|
||||
else:
|
||||
self.available = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Not found attributes are redirected to the loader
|
||||
"""
|
||||
return getattr(self.loader, name)
|
||||
|
||||
def get_source(self, name, parent=None):
|
||||
"""Retrieve the sourcecode of a template."""
|
||||
# just ascii chars are allowed as template names
|
||||
name = str(name)
|
||||
return self.loader.get_source(self.environment, name, parent)
|
||||
|
||||
def parse(self, name, parent=None):
|
||||
"""Retreive a template and parse it."""
|
||||
# just ascii chars are allowed as template names
|
||||
name = str(name)
|
||||
return self.loader.parse(self.environment, name, parent)
|
||||
|
||||
def load(self, name, translator=PythonTranslator):
|
||||
"""
|
||||
Translate a template and return it. This must not necesarily
|
||||
be a template class. The javascript translator for example
|
||||
will just output a string with the translated code.
|
||||
"""
|
||||
# just ascii chars are allowed as template names
|
||||
name = str(name)
|
||||
try:
|
||||
return self.loader.load(self.environment, name, translator)
|
||||
except TemplateSyntaxError, e:
|
||||
if not self.environment.friendly_traceback:
|
||||
raise
|
||||
__traceback_hide__ = True
|
||||
from jinja.debugger import raise_syntax_error
|
||||
raise_syntax_error(e, self.environment)
|
||||
|
||||
def get_controlled_loader(self):
|
||||
"""
|
||||
Return a loader that runs in a controlled environment. (Keeps
|
||||
track of templates that it loads and is not thread safe).
|
||||
"""
|
||||
return ControlledLoader(self.environment, self.loader)
|
||||
|
||||
def _loader_missing(self, *args, **kwargs):
|
||||
"""Helper method that overrides all other methods if no
|
||||
loader is defined."""
|
||||
raise RuntimeError('no loader defined')
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.available
|
||||
|
||||
|
||||
class ControlledLoader(LoaderWrapper):
|
||||
"""
|
||||
Used for template extending and including.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, loader):
|
||||
LoaderWrapper.__init__(self, environment, loader)
|
||||
self._stack = []
|
||||
|
||||
def get_controlled_loader(self):
|
||||
raise TypeError('Cannot get new controlled loader from an already '
|
||||
'controlled loader.')
|
||||
|
||||
def mark_as_processed(self):
|
||||
"""Mark the last parsed/sourced/included template as processed."""
|
||||
if not self._stack:
|
||||
raise RuntimeError('No template for marking found')
|
||||
self._stack.pop()
|
||||
|
||||
def _controlled(method):
|
||||
def new_method(self, name, *args, **kw):
|
||||
if name in self._stack:
|
||||
raise TemplateIncludeError('Circular imports/extends '
|
||||
'detected. %r appeared twice.' %
|
||||
name)
|
||||
self._stack.append(name)
|
||||
return method(self, name, *args, **kw)
|
||||
try:
|
||||
new_method.__name__ = method.__name__
|
||||
new_method.__doc__ = method.__doc__
|
||||
except:
|
||||
pass
|
||||
return new_method
|
||||
|
||||
get_source = _controlled(LoaderWrapper.get_source)
|
||||
parse = _controlled(LoaderWrapper.parse)
|
||||
load = _controlled(LoaderWrapper.load)
|
||||
del _controlled
|
||||
|
||||
|
||||
class BaseLoader(object):
|
||||
"""
|
||||
Use this class to implement loaders.
|
||||
|
||||
Just inherit from this class and implement a method called
|
||||
`get_source` with the signature (`environment`, `name`, `parent`)
|
||||
that returns sourcecode for the template.
|
||||
|
||||
For more complex loaders you probably want to override `load` to
|
||||
or not use the `BaseLoader` at all.
|
||||
"""
|
||||
|
||||
def parse(self, environment, name, parent):
|
||||
"""
|
||||
Load and parse a template
|
||||
"""
|
||||
source = self.get_source(environment, name, parent)
|
||||
return Parser(environment, source, name).parse()
|
||||
|
||||
def load(self, environment, name, translator):
|
||||
"""
|
||||
Load and translate a template
|
||||
"""
|
||||
ast = self.parse(environment, name, None)
|
||||
return translator.process(environment, ast)
|
||||
|
||||
def get_source(self, environment, name, parent):
|
||||
"""
|
||||
Override this method to get the source for a template.
|
||||
"""
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
|
||||
class CachedLoaderMixin(object):
|
||||
"""
|
||||
Mixin this class to implement simple memory and disk caching. The
|
||||
memcaching just uses a dict in the loader so if you have a global
|
||||
environment or at least a global loader this can speed things up.
|
||||
|
||||
If the memcaching is enabled you can use (with Jinja 1.1 onwards)
|
||||
the `clear_memcache` function to clear the cache.
|
||||
|
||||
For memcached support check the `MemcachedLoaderMixin`.
|
||||
"""
|
||||
|
||||
def __init__(self, use_memcache, cache_size, cache_folder, auto_reload,
|
||||
cache_salt=None):
|
||||
if use_memcache:
|
||||
self.__memcache = CacheDict(cache_size)
|
||||
else:
|
||||
self.__memcache = None
|
||||
self.__cache_folder = cache_folder
|
||||
if not hasattr(self, 'check_source_changed'):
|
||||
self.__auto_reload = False
|
||||
else:
|
||||
self.__auto_reload = auto_reload
|
||||
self.__salt = cache_salt
|
||||
self.__times = {}
|
||||
self.__lock = Lock()
|
||||
|
||||
def clear_memcache(self):
|
||||
"""
|
||||
Clears the memcache.
|
||||
"""
|
||||
if self.__memcache is not None:
|
||||
self.__memcache.clear()
|
||||
|
||||
def load(self, environment, name, translator):
|
||||
"""
|
||||
Load and translate a template. First we check if there is a
|
||||
cached version of this template in the memory cache. If this is
|
||||
not the cache check for a compiled template in the disk cache
|
||||
folder. And if none of this is the case we translate the temlate,
|
||||
cache and return it.
|
||||
"""
|
||||
self.__lock.acquire()
|
||||
try:
|
||||
# caching is only possible for the python translator. skip
|
||||
# all other translators
|
||||
if not issubclass(translator, PythonTranslator):
|
||||
return super(CachedLoaderMixin, self).load(
|
||||
environment, name, translator)
|
||||
|
||||
tmpl = None
|
||||
save_to_disk = False
|
||||
push_to_memory = False
|
||||
|
||||
# auto reload enabled? check for the last change of
|
||||
# the template
|
||||
if self.__auto_reload:
|
||||
last_change = self.check_source_changed(environment, name)
|
||||
else:
|
||||
last_change = None
|
||||
|
||||
# check if we have something in the memory cache and the
|
||||
# memory cache is enabled.
|
||||
if self.__memcache is not None:
|
||||
if name in self.__memcache:
|
||||
tmpl = self.__memcache[name]
|
||||
# if auto reload is enabled check if the template changed
|
||||
if last_change and last_change > self.__times[name]:
|
||||
tmpl = None
|
||||
push_to_memory = True
|
||||
else:
|
||||
push_to_memory = True
|
||||
|
||||
# mem cache disabled or not cached by now
|
||||
# try to load if from the disk cache
|
||||
if tmpl is None and self.__cache_folder is not None:
|
||||
cache_fn = get_cachename(self.__cache_folder, name, self.__salt)
|
||||
if last_change is not None:
|
||||
try:
|
||||
cache_time = path.getmtime(cache_fn)
|
||||
except OSError:
|
||||
cache_time = 0
|
||||
if last_change is None or (cache_time and
|
||||
last_change <= cache_time):
|
||||
try:
|
||||
f = file(cache_fn, 'rb')
|
||||
except IOError:
|
||||
tmpl = None
|
||||
save_to_disk = True
|
||||
else:
|
||||
try:
|
||||
tmpl = Template.load(environment, f)
|
||||
finally:
|
||||
f.close()
|
||||
else:
|
||||
save_to_disk = True
|
||||
|
||||
# if we still have no template we load, parse and translate it.
|
||||
if tmpl is None:
|
||||
tmpl = super(CachedLoaderMixin, self).load(
|
||||
environment, name, translator)
|
||||
|
||||
# save the compiled template on the disk if enabled
|
||||
if save_to_disk:
|
||||
f = file(cache_fn, 'wb')
|
||||
try:
|
||||
tmpl.dump(f)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
# if memcaching is enabled and the template not loaded
|
||||
# we add that there.
|
||||
if push_to_memory:
|
||||
self.__times[name] = time.time()
|
||||
self.__memcache[name] = tmpl
|
||||
return tmpl
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
|
||||
class MemcachedLoaderMixin(object):
|
||||
"""
|
||||
Uses a memcached server to cache the templates.
|
||||
|
||||
Requires the memcache library from `tummy`_ or the cmemcache library
|
||||
from `Gijsbert de Haan`_.
|
||||
|
||||
With Jinja 1.2 onwards you can also provide a `client` keyword argument
|
||||
that takes an already instanciated memcache client or memcache client
|
||||
like object.
|
||||
|
||||
.. _tummy: http://www.tummy.com/Community/software/python-memcached/
|
||||
.. _Gisjsbert de Haan: http://gijsbert.org/cmemcache/
|
||||
"""
|
||||
|
||||
def __init__(self, use_memcache, memcache_time=60 * 60 * 24 * 7,
|
||||
memcache_host=None, item_prefix='template/', client=None):
|
||||
if memcache_host is None:
|
||||
memcache_host = ['127.0.0.1:11211']
|
||||
if use_memcache:
|
||||
if client is None:
|
||||
try:
|
||||
try:
|
||||
from cmemcache import Client
|
||||
except ImportError:
|
||||
from memcache import Client
|
||||
except ImportError:
|
||||
raise RuntimeError('the %r loader requires an installed '
|
||||
'memcache module' %
|
||||
self.__class__.__name__)
|
||||
client = Client(list(memcache_host))
|
||||
self.__memcache = client
|
||||
self.__memcache_time = memcache_time
|
||||
else:
|
||||
self.__memcache = None
|
||||
self.__item_prefix = item_prefix
|
||||
self.__lock = Lock()
|
||||
|
||||
def load(self, environment, name, translator):
|
||||
"""
|
||||
Load and translate a template. First we check if there is a
|
||||
cached version of this template in the memory cache. If this is
|
||||
not the cache check for a compiled template in the disk cache
|
||||
folder. And if none of this is the case we translate the template,
|
||||
cache and return it.
|
||||
"""
|
||||
self.__lock.acquire()
|
||||
try:
|
||||
# caching is only possible for the python translator. skip
|
||||
# all other translators
|
||||
if not issubclass(translator, PythonTranslator):
|
||||
return super(MemcachedLoaderMixin, self).load(
|
||||
environment, name, translator)
|
||||
tmpl = None
|
||||
push_to_memory = False
|
||||
|
||||
# check if we have something in the memory cache and the
|
||||
# memory cache is enabled.
|
||||
if self.__memcache is not None:
|
||||
bytecode = self.__memcache.get(self.__item_prefix + name)
|
||||
if bytecode:
|
||||
tmpl = Template.load(environment, bytecode)
|
||||
else:
|
||||
push_to_memory = True
|
||||
|
||||
# if we still have no template we load, parse and translate it.
|
||||
if tmpl is None:
|
||||
tmpl = super(MemcachedLoaderMixin, self).load(
|
||||
environment, name, translator)
|
||||
|
||||
# if memcaching is enabled and the template not loaded
|
||||
# we add that there.
|
||||
if push_to_memory:
|
||||
self.__memcache.set(self.__item_prefix + name, tmpl.dump(),
|
||||
self.__memcache_time)
|
||||
return tmpl
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
|
||||
class BaseFileSystemLoader(BaseLoader):
|
||||
"""
|
||||
Baseclass for the file system loader that does not do any caching.
|
||||
It exists to avoid redundant code, just don't use it without subclassing.
|
||||
|
||||
How subclassing can work:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja.loaders import BaseFileSystemLoader
|
||||
|
||||
class MyFileSystemLoader(BaseFileSystemLoader):
|
||||
def __init__(self):
|
||||
BaseFileSystemLoader.__init__(self, '/path/to/templates')
|
||||
|
||||
The base file system loader only takes one parameter beside self which
|
||||
is the path to the templates.
|
||||
"""
|
||||
|
||||
def __init__(self, searchpath):
|
||||
self.searchpath = path.abspath(searchpath)
|
||||
|
||||
def get_source(self, environment, name, parent):
|
||||
filename = get_template_filename(self.searchpath, name)
|
||||
if path.isfile(filename):
|
||||
f = codecs.open(filename, 'r', environment.template_charset)
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
else:
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
|
||||
class FileSystemLoader(CachedLoaderMixin, BaseFileSystemLoader):
|
||||
"""
|
||||
Loads templates from the filesystem:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja import Environment, FileSystemLoader
|
||||
e = Environment(loader=FileSystemLoader('templates/'))
|
||||
|
||||
You can pass the following keyword arguments to the loader on
|
||||
initialization:
|
||||
|
||||
=================== =================================================
|
||||
``searchpath`` String with the path to the templates on the
|
||||
filesystem.
|
||||
``use_memcache`` Set this to ``True`` to enable memory caching.
|
||||
This is usually a good idea in production mode,
|
||||
but disable it during development since it won't
|
||||
reload template changes automatically.
|
||||
This only works in persistent environments like
|
||||
FastCGI.
|
||||
``memcache_size`` Number of template instance you want to cache.
|
||||
Defaults to ``40``.
|
||||
``cache_folder`` Set this to an existing directory to enable
|
||||
caching of templates on the file system. Note
|
||||
that this only affects templates transformed
|
||||
into python code. Default is ``None`` which means
|
||||
that caching is disabled.
|
||||
``auto_reload`` Set this to `False` for a slightly better
|
||||
performance. In that case Jinja won't check for
|
||||
template changes on the filesystem.
|
||||
``cache_salt`` Optional unique number to not confuse the
|
||||
caching system when caching more than one
|
||||
template loader in the same folder. Defaults
|
||||
to the searchpath. *New in Jinja 1.1*
|
||||
=================== =================================================
|
||||
"""
|
||||
|
||||
def __init__(self, searchpath, use_memcache=False, memcache_size=40,
|
||||
cache_folder=None, auto_reload=True, cache_salt=None):
|
||||
BaseFileSystemLoader.__init__(self, searchpath)
|
||||
|
||||
if cache_salt is None:
|
||||
cache_salt = self.searchpath
|
||||
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
|
||||
cache_folder, auto_reload, cache_salt)
|
||||
|
||||
def check_source_changed(self, environment, name):
|
||||
filename = get_template_filename(self.searchpath, name)
|
||||
if path.isfile(filename):
|
||||
return path.getmtime(filename)
|
||||
return -1
|
||||
|
||||
|
||||
class MemcachedFileSystemLoader(MemcachedLoaderMixin, BaseFileSystemLoader):
|
||||
"""
|
||||
Loads templates from the filesystem and caches them on a memcached
|
||||
server.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja import Environment, MemcachedFileSystemLoader
|
||||
e = Environment(loader=MemcachedFileSystemLoader('templates/',
|
||||
memcache_host=['192.168.2.250:11211']
|
||||
))
|
||||
|
||||
You can pass the following keyword arguments to the loader on
|
||||
initialization:
|
||||
|
||||
=================== =================================================
|
||||
``searchpath`` String with the path to the templates on the
|
||||
filesystem.
|
||||
``use_memcache`` Set this to ``True`` to enable memcached caching.
|
||||
In that case it behaves like a normal
|
||||
`FileSystemLoader` with disabled caching.
|
||||
``memcache_time`` The expire time of a template in the cache.
|
||||
``memcache_host`` a list of memcached servers.
|
||||
``item_prefix`` The prefix for the items on the server. Defaults
|
||||
to ``'template/'``.
|
||||
=================== =================================================
|
||||
"""
|
||||
|
||||
def __init__(self, searchpath, use_memcache=True,
|
||||
memcache_time=60 * 60 * 24 * 7, memcache_host=None,
|
||||
item_prefix='template/'):
|
||||
BaseFileSystemLoader.__init__(self, searchpath)
|
||||
MemcachedLoaderMixin.__init__(self, use_memcache, memcache_time,
|
||||
memcache_host, item_prefix)
|
||||
|
||||
|
||||
class BasePackageLoader(BaseLoader):
|
||||
"""
|
||||
Baseclass for the package loader that does not do any caching.
|
||||
|
||||
It accepts two parameters: The name of the package and the path relative
|
||||
to the package:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja.loaders import BasePackageLoader
|
||||
|
||||
class MyPackageLoader(BasePackageLoader):
|
||||
def __init__(self):
|
||||
BasePackageLoader.__init__(self, 'my_package', 'shared/templates')
|
||||
|
||||
The relative path must use slashes as path delimiters, even on Mac OS
|
||||
and Microsoft Windows.
|
||||
|
||||
It uses the `pkg_resources` libraries distributed with setuptools for
|
||||
retrieving the data from the packages. This works for eggs too so you
|
||||
don't have to mark your egg as non zip safe. If pkg_resources is not
|
||||
available it just falls back to path joining relative to the package.
|
||||
"""
|
||||
|
||||
def __init__(self, package_name, package_path, force_native=False):
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
raise RuntimeError('setuptools not installed')
|
||||
self.package_name = package_name
|
||||
self.package_path = package_path
|
||||
self.force_native = force_native
|
||||
|
||||
def _get_load_func(self):
|
||||
if hasattr(self, '_load_func'):
|
||||
return self._load_func
|
||||
try:
|
||||
from pkg_resources import resource_exists, resource_string
|
||||
if self.force_native:
|
||||
raise ImportError()
|
||||
except ImportError:
|
||||
basepath = path.dirname(__import__(self.package_name, None, None,
|
||||
['__file__']).__file__)
|
||||
def load_func(name):
|
||||
filename = path.join(basepath, *(
|
||||
self.package_path.split('/') +
|
||||
[p for p in name.split('/') if p != '..'])
|
||||
)
|
||||
if path.isfile(filename):
|
||||
f = file(filename)
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
else:
|
||||
def load_func(name):
|
||||
path = '/'.join([self.package_path] + [p for p in name.split('/')
|
||||
if p != '..'])
|
||||
if resource_exists(self.package_name, path):
|
||||
return resource_string(self.package_name, path)
|
||||
self._load_func = load_func
|
||||
return load_func
|
||||
|
||||
def get_source(self, environment, name, parent):
|
||||
load_func = self._get_load_func()
|
||||
contents = load_func(name)
|
||||
if contents is None:
|
||||
raise TemplateNotFound(name)
|
||||
return contents.decode(environment.template_charset)
|
||||
|
||||
|
||||
class PackageLoader(CachedLoaderMixin, BasePackageLoader):
|
||||
"""
|
||||
Loads templates from python packages using setuptools.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja import Environment, PackageLoader
|
||||
e = Environment(loader=PackageLoader('yourapp', 'template/path'))
|
||||
|
||||
You can pass the following keyword arguments to the loader on
|
||||
initialization:
|
||||
|
||||
=================== =================================================
|
||||
``package_name`` Name of the package containing the templates.
|
||||
``package_path`` Path of the templates inside the package.
|
||||
``use_memcache`` Set this to ``True`` to enable memory caching.
|
||||
This is usually a good idea in production mode,
|
||||
but disable it during development since it won't
|
||||
reload template changes automatically.
|
||||
This only works in persistent environments like
|
||||
FastCGI.
|
||||
``memcache_size`` Number of template instance you want to cache.
|
||||
Defaults to ``40``.
|
||||
``cache_folder`` Set this to an existing directory to enable
|
||||
caching of templates on the file system. Note
|
||||
that this only affects templates transformed
|
||||
into python code. Default is ``None`` which means
|
||||
that caching is disabled.
|
||||
``auto_reload`` Set this to `False` for a slightly better
|
||||
performance. In that case Jinja won't check for
|
||||
template changes on the filesystem. If the
|
||||
templates are inside of an egg file this won't
|
||||
have an effect.
|
||||
``cache_salt`` Optional unique number to not confuse the
|
||||
caching system when caching more than one
|
||||
template loader in the same folder. Defaults
|
||||
to ``package_name + '/' + package_path``.
|
||||
*New in Jinja 1.1*
|
||||
=================== =================================================
|
||||
|
||||
Important note: If you're using an application that is inside of an
|
||||
egg never set `auto_reload` to `True`. The egg resource manager will
|
||||
automatically export files to the file system and touch them so that
|
||||
you not only end up with additional temporary files but also an automatic
|
||||
reload each time you load a template.
|
||||
"""
|
||||
|
||||
def __init__(self, package_name, package_path, use_memcache=False,
|
||||
memcache_size=40, cache_folder=None, auto_reload=True,
|
||||
cache_salt=None):
|
||||
BasePackageLoader.__init__(self, package_name, package_path)
|
||||
|
||||
if cache_salt is None:
|
||||
cache_salt = package_name + '/' + package_path
|
||||
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
|
||||
cache_folder, auto_reload, cache_salt)
|
||||
|
||||
def check_source_changed(self, environment, name):
|
||||
from pkg_resources import resource_exists, resource_filename
|
||||
fn = resource_filename(self.package_name, '/'.join([self.package_path] +
|
||||
[p for p in name.split('/') if p and p[0] != '.']))
|
||||
if resource_exists(self.package_name, fn):
|
||||
return path.getmtime(fn)
|
||||
return -1
|
||||
|
||||
|
||||
class BaseFunctionLoader(BaseLoader):
|
||||
"""
|
||||
Baseclass for the function loader that doesn't do any caching.
|
||||
|
||||
It just accepts one parameter which is the function which is called
|
||||
with the name of the requested template. If the return value is `None`
|
||||
the loader will raise a `TemplateNotFound` error.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja.loaders import BaseFunctionLoader
|
||||
|
||||
templates = {...}
|
||||
|
||||
class MyFunctionLoader(BaseFunctionLoader):
|
||||
def __init__(self):
|
||||
BaseFunctionLoader(templates.get)
|
||||
"""
|
||||
|
||||
def __init__(self, loader_func):
|
||||
self.loader_func = loader_func
|
||||
|
||||
def get_source(self, environment, name, parent):
|
||||
rv = self.loader_func(name)
|
||||
if rv is None:
|
||||
raise TemplateNotFound(name)
|
||||
if isinstance(rv, str):
|
||||
return rv.decode(environment.template_charset)
|
||||
return rv
|
||||
|
||||
|
||||
class FunctionLoader(CachedLoaderMixin, BaseFunctionLoader):
|
||||
"""
|
||||
Loads templates by calling a function which has to return a string
|
||||
or `None` if an error occoured.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja import Environment, FunctionLoader
|
||||
|
||||
def my_load_func(template_name):
|
||||
if template_name == 'foo':
|
||||
return '...'
|
||||
|
||||
e = Environment(loader=FunctionLoader(my_load_func))
|
||||
|
||||
Because the interface is limited there is no way to cache such
|
||||
templates. Usually you should try to use a loader with a more
|
||||
solid backend.
|
||||
|
||||
You can pass the following keyword arguments to the loader on
|
||||
initialization:
|
||||
|
||||
=================== =================================================
|
||||
``loader_func`` Function that takes the name of the template to
|
||||
load. If it returns a string or unicode object
|
||||
it's used to load a template. If the return
|
||||
value is None it's considered missing.
|
||||
``getmtime_func`` Function used to check if templates requires
|
||||
reloading. Has to return the UNIX timestamp of
|
||||
the last template change or ``-1`` if this template
|
||||
does not exist or requires updates at any cost.
|
||||
``use_memcache`` Set this to ``True`` to enable memory caching.
|
||||
This is usually a good idea in production mode,
|
||||
but disable it during development since it won't
|
||||
reload template changes automatically.
|
||||
This only works in persistent environments like
|
||||
FastCGI.
|
||||
``memcache_size`` Number of template instance you want to cache.
|
||||
Defaults to ``40``.
|
||||
``cache_folder`` Set this to an existing directory to enable
|
||||
caching of templates on the file system. Note
|
||||
that this only affects templates transformed
|
||||
into python code. Default is ``None`` which means
|
||||
that caching is disabled.
|
||||
``auto_reload`` Set this to `False` for a slightly better
|
||||
performance. In that case of `getmtime_func`
|
||||
not being provided this won't have an effect.
|
||||
``cache_salt`` Optional unique number to not confuse the
|
||||
caching system when caching more than one
|
||||
template loader in the same folder.
|
||||
=================== =================================================
|
||||
"""
|
||||
|
||||
def __init__(self, loader_func, getmtime_func=None, use_memcache=False,
|
||||
memcache_size=40, cache_folder=None, auto_reload=True,
|
||||
cache_salt=None):
|
||||
BaseFunctionLoader.__init__(self, loader_func)
|
||||
# when changing the signature also check the jinja.plugin function
|
||||
# loader instantiation.
|
||||
self.getmtime_func = getmtime_func
|
||||
if auto_reload and getmtime_func is None:
|
||||
auto_reload = False
|
||||
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
|
||||
cache_folder, auto_reload, cache_salt)
|
||||
|
||||
def check_source_changed(self, environment, name):
|
||||
return self.getmtime_func(name)
|
||||
|
||||
|
||||
class DictLoader(BaseLoader):
|
||||
"""
|
||||
Load templates from a given dict:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja import Environment, DictLoader
|
||||
e = Environment(loader=DictLoader(dict(
|
||||
layout='...',
|
||||
index='{% extends 'layout' %}...'
|
||||
)))
|
||||
|
||||
This loader does not have any caching capabilities.
|
||||
"""
|
||||
|
||||
def __init__(self, templates):
|
||||
self.templates = templates
|
||||
|
||||
def get_source(self, environment, name, parent):
|
||||
if name in self.templates:
|
||||
return self.templates[name]
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
|
||||
class ChoiceLoader(object):
|
||||
"""
|
||||
A loader that tries multiple loaders in the order they are given to
|
||||
the `ChoiceLoader`:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from jinja import ChoiceLoader, FileSystemLoader
|
||||
loader1 = FileSystemLoader("templates1")
|
||||
loader2 = FileSystemLoader("templates2")
|
||||
loader = ChoiceLoader([loader1, loader2])
|
||||
"""
|
||||
|
||||
def __init__(self, loaders):
|
||||
self.loaders = list(loaders)
|
||||
|
||||
def get_source(self, environment, name, parent):
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.get_source(environment, name, parent)
|
||||
except TemplateNotFound, e:
|
||||
if e.name != name:
|
||||
raise
|
||||
continue
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
def parse(self, environment, name, parent):
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.parse(environment, name, parent)
|
||||
except TemplateNotFound, e:
|
||||
if e.name != name:
|
||||
raise
|
||||
continue
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
def load(self, environment, name, translator):
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.load(environment, name, translator)
|
||||
except TemplateNotFound, e:
|
||||
if e.name != name:
|
||||
raise
|
||||
continue
|
||||
raise TemplateNotFound(name)
|
792
jinja/nodes.py
792
jinja/nodes.py
@ -1,792 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.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: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from itertools import chain
|
||||
from copy import copy
|
||||
|
||||
|
||||
def get_nodes(nodetype, tree, exclude_root=True):
|
||||
"""
|
||||
Get all nodes from nodetype in the tree excluding the
|
||||
node passed if `exclude_root` is `True` (default).
|
||||
"""
|
||||
if exclude_root:
|
||||
todo = tree.get_child_nodes()
|
||||
else:
|
||||
todo = [tree]
|
||||
while todo:
|
||||
node = todo.pop()
|
||||
if node.__class__ is nodetype:
|
||||
yield node
|
||||
todo.extend(node.get_child_nodes())
|
||||
|
||||
|
||||
class NotPossible(NotImplementedError):
|
||||
"""
|
||||
If a given node cannot do something.
|
||||
"""
|
||||
|
||||
|
||||
class Node(object):
|
||||
"""
|
||||
Jinja node.
|
||||
"""
|
||||
|
||||
def __init__(self, lineno=None, filename=None):
|
||||
self.lineno = lineno
|
||||
self.filename = filename
|
||||
|
||||
def get_items(self):
|
||||
return []
|
||||
|
||||
def get_child_nodes(self):
|
||||
return [x for x in self.get_items() if isinstance(x, Node)]
|
||||
|
||||
def allows_assignments(self):
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return 'Node()'
|
||||
|
||||
|
||||
class Text(Node):
|
||||
"""
|
||||
Node that represents normal text.
|
||||
"""
|
||||
|
||||
def __init__(self, text, variables, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.text = text
|
||||
self.variables = variables
|
||||
|
||||
def get_items(self):
|
||||
return [self.text] + list(self.variables)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Text(%r, %r)' % (
|
||||
self.text,
|
||||
self.variables
|
||||
)
|
||||
|
||||
|
||||
class NodeList(list, Node):
|
||||
"""
|
||||
A node that stores multiple childnodes.
|
||||
"""
|
||||
|
||||
def __init__(self, data, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
list.__init__(self, data)
|
||||
|
||||
def get_items(self):
|
||||
return list(self)
|
||||
|
||||
def __repr__(self):
|
||||
return 'NodeList(%s)' % list.__repr__(self)
|
||||
|
||||
|
||||
class Template(Node):
|
||||
"""
|
||||
Node that represents a template.
|
||||
"""
|
||||
|
||||
def __init__(self, extends, body, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.extends = extends
|
||||
self.body = body
|
||||
|
||||
def get_items(self):
|
||||
return [self.extends, self.body]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Template(%r, %r)' % (
|
||||
self.extends,
|
||||
self.body
|
||||
)
|
||||
|
||||
|
||||
class ForLoop(Node):
|
||||
"""
|
||||
A node that represents a for loop
|
||||
"""
|
||||
|
||||
def __init__(self, item, seq, body, else_, recursive, lineno=None,
|
||||
filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.item = item
|
||||
self.seq = seq
|
||||
self.body = body
|
||||
self.else_ = else_
|
||||
self.recursive = recursive
|
||||
|
||||
def get_items(self):
|
||||
return [self.item, self.seq, self.body, self.else_, self.recursive]
|
||||
|
||||
def __repr__(self):
|
||||
return 'ForLoop(%r, %r, %r, %r, %r)' % (
|
||||
self.item,
|
||||
self.seq,
|
||||
self.body,
|
||||
self.else_,
|
||||
self.recursive
|
||||
)
|
||||
|
||||
|
||||
class IfCondition(Node):
|
||||
"""
|
||||
A node that represents an if condition.
|
||||
"""
|
||||
|
||||
def __init__(self, tests, else_, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.tests = tests
|
||||
self.else_ = else_
|
||||
|
||||
def get_items(self):
|
||||
result = []
|
||||
for test in self.tests:
|
||||
result.extend(test)
|
||||
result.append(self.else_)
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
return 'IfCondition(%r, %r)' % (
|
||||
self.tests,
|
||||
self.else_
|
||||
)
|
||||
|
||||
|
||||
class Cycle(Node):
|
||||
"""
|
||||
A node that represents the cycle statement.
|
||||
"""
|
||||
|
||||
def __init__(self, seq, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.seq = seq
|
||||
|
||||
def get_items(self):
|
||||
return [self.seq]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Cycle(%r)' % (self.seq,)
|
||||
|
||||
|
||||
class Print(Node):
|
||||
"""
|
||||
A node that represents variable tags and print calls.
|
||||
"""
|
||||
|
||||
def __init__(self, expr, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.expr = expr
|
||||
|
||||
def get_items(self):
|
||||
return [self.expr]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Print(%r)' % (self.expr,)
|
||||
|
||||
|
||||
class Macro(Node):
|
||||
"""
|
||||
A node that represents a macro.
|
||||
"""
|
||||
|
||||
def __init__(self, name, arguments, body, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.name = name
|
||||
self.arguments = arguments
|
||||
self.body = body
|
||||
|
||||
def get_items(self):
|
||||
return [self.name] + list(chain(*self.arguments)) + [self.body]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Macro(%r, %r, %r)' % (
|
||||
self.name,
|
||||
self.arguments,
|
||||
self.body
|
||||
)
|
||||
|
||||
|
||||
class Call(Node):
|
||||
"""
|
||||
A node that represents am extended macro call.
|
||||
"""
|
||||
|
||||
def __init__(self, expr, body, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.expr = expr
|
||||
self.body = body
|
||||
|
||||
def get_items(self):
|
||||
return [self.expr, self.body]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Call(%r, %r)' % (
|
||||
self.expr,
|
||||
self.body
|
||||
)
|
||||
|
||||
|
||||
class Set(Node):
|
||||
"""
|
||||
Allows defining own variables.
|
||||
"""
|
||||
|
||||
def __init__(self, name, expr, scope_local, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.name = name
|
||||
self.expr = expr
|
||||
self.scope_local = scope_local
|
||||
|
||||
def get_items(self):
|
||||
return [self.name, self.expr, self.scope_local]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Set(%r, %r, %r)' % (
|
||||
self.name,
|
||||
self.expr,
|
||||
self.scope_local
|
||||
)
|
||||
|
||||
|
||||
class Filter(Node):
|
||||
"""
|
||||
Node for filter sections.
|
||||
"""
|
||||
|
||||
def __init__(self, body, filters, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.body = body
|
||||
self.filters = filters
|
||||
|
||||
def get_items(self):
|
||||
return [self.body] + list(self.filters)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Filter(%r, %r)' % (
|
||||
self.body,
|
||||
self.filters
|
||||
)
|
||||
|
||||
|
||||
class Block(Node):
|
||||
"""
|
||||
A node that represents a block.
|
||||
"""
|
||||
|
||||
def __init__(self, name, body, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.name = name
|
||||
self.body = body
|
||||
|
||||
def replace(self, node):
|
||||
"""
|
||||
Replace the current data with the copied data of another block
|
||||
node.
|
||||
"""
|
||||
assert node.__class__ is Block
|
||||
self.lineno = node.lineno
|
||||
self.filename = node.filename
|
||||
self.name = node.name
|
||||
self.body = copy(node.body)
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Create an independent clone of this node.
|
||||
"""
|
||||
return copy(self)
|
||||
|
||||
def get_items(self):
|
||||
return [self.name, self.body]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Block(%r, %r)' % (
|
||||
self.name,
|
||||
self.body
|
||||
)
|
||||
|
||||
|
||||
class Include(Node):
|
||||
"""
|
||||
A node that represents the include tag.
|
||||
"""
|
||||
|
||||
def __init__(self, template, lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.template = template
|
||||
|
||||
def get_items(self):
|
||||
return [self.template]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Include(%r)' % (
|
||||
self.template
|
||||
)
|
||||
|
||||
|
||||
class Trans(Node):
|
||||
"""
|
||||
A node for translatable sections.
|
||||
"""
|
||||
|
||||
def __init__(self, singular, plural, indicator, replacements,
|
||||
lineno=None, filename=None):
|
||||
Node.__init__(self, lineno, filename)
|
||||
self.singular = singular
|
||||
self.plural = plural
|
||||
self.indicator = indicator
|
||||
self.replacements = replacements
|
||||
|
||||
def get_items(self):
|
||||
rv = [self.singular, self.plural, self.indicator]
|
||||
if self.replacements:
|
||||
rv.extend(self.replacements.values())
|
||||
rv.extend(self.replacements.keys())
|
||||
return rv
|
||||
|
||||
def __repr__(self):
|
||||
return 'Trans(%r, %r, %r, %r)' % (
|
||||
self.singular,
|
||||
self.plural,
|
||||
self.indicator,
|
||||
self.replacements
|
||||
)
|
||||
|
||||
|
||||
class Expression(Node):
|
||||
"""
|
||||
Baseclass for all expressions.
|
||||
"""
|
||||
|
||||
|
||||
class BinaryExpression(Expression):
|
||||
"""
|
||||
Baseclass for all binary expressions.
|
||||
"""
|
||||
|
||||
def __init__(self, left, right, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.left = left
|
||||
self.right = right
|
||||
|
||||
def get_items(self):
|
||||
return [self.left, self.right]
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (
|
||||
self.__class__.__name__,
|
||||
self.left,
|
||||
self.right
|
||||
)
|
||||
|
||||
|
||||
class UnaryExpression(Expression):
|
||||
"""
|
||||
Baseclass for all unary expressions.
|
||||
"""
|
||||
|
||||
def __init__(self, node, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.node = node
|
||||
|
||||
def get_items(self):
|
||||
return [self.node]
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (
|
||||
self.__class__.__name__,
|
||||
self.node
|
||||
)
|
||||
|
||||
|
||||
class ConstantExpression(Expression):
|
||||
"""
|
||||
any constat such as {{ "foo" }}
|
||||
"""
|
||||
|
||||
def __init__(self, value, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.value = value
|
||||
|
||||
def get_items(self):
|
||||
return [self.value]
|
||||
|
||||
def __repr__(self):
|
||||
return 'ConstantExpression(%r)' % (self.value,)
|
||||
|
||||
|
||||
class UndefinedExpression(Expression):
|
||||
"""
|
||||
represents the special 'undefined' value.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return 'UndefinedExpression()'
|
||||
|
||||
|
||||
class RegexExpression(Expression):
|
||||
"""
|
||||
represents the regular expression literal.
|
||||
"""
|
||||
|
||||
def __init__(self, value, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.value = value
|
||||
|
||||
def get_items(self):
|
||||
return [self.value]
|
||||
|
||||
def __repr__(self):
|
||||
return 'RegexExpression(%r)' % (self.value,)
|
||||
|
||||
|
||||
class NameExpression(Expression):
|
||||
"""
|
||||
any name such as {{ foo }}
|
||||
"""
|
||||
|
||||
def __init__(self, name, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.name = name
|
||||
|
||||
def get_items(self):
|
||||
return [self.name]
|
||||
|
||||
def allows_assignments(self):
|
||||
return self.name != '_'
|
||||
|
||||
def __repr__(self):
|
||||
return 'NameExpression(%r)' % self.name
|
||||
|
||||
|
||||
class ListExpression(Expression):
|
||||
"""
|
||||
any list literal such as {{ [1, 2, 3] }}
|
||||
"""
|
||||
|
||||
def __init__(self, items, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.items = items
|
||||
|
||||
def get_items(self):
|
||||
return list(self.items)
|
||||
|
||||
def __repr__(self):
|
||||
return 'ListExpression(%r)' % (self.items,)
|
||||
|
||||
|
||||
class DictExpression(Expression):
|
||||
"""
|
||||
any dict literal such as {{ {1: 2, 3: 4} }}
|
||||
"""
|
||||
|
||||
def __init__(self, items, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.items = items
|
||||
|
||||
def get_items(self):
|
||||
return list(chain(*self.items))
|
||||
|
||||
def __repr__(self):
|
||||
return 'DictExpression(%r)' % (self.items,)
|
||||
|
||||
|
||||
class SetExpression(Expression):
|
||||
"""
|
||||
any set literal such as {{ @(1, 2, 3) }}
|
||||
"""
|
||||
|
||||
def __init__(self, items, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.items = items
|
||||
|
||||
def get_items(self):
|
||||
return self.items[:]
|
||||
|
||||
def __repr__(self):
|
||||
return 'SetExpression(%r)' % (self.items,)
|
||||
|
||||
|
||||
class ConditionalExpression(Expression):
|
||||
"""
|
||||
{{ foo if bar else baz }}
|
||||
"""
|
||||
|
||||
def __init__(self, test, expr1, expr2, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.test = test
|
||||
self.expr1 = expr1
|
||||
self.expr2 = expr2
|
||||
|
||||
def get_items(self):
|
||||
return [self.test, self.expr1, self.expr2]
|
||||
|
||||
def __repr__(self):
|
||||
return 'ConstantExpression(%r, %r, %r)' % (
|
||||
self.test,
|
||||
self.expr1,
|
||||
self.expr2
|
||||
)
|
||||
|
||||
|
||||
class FilterExpression(Expression):
|
||||
"""
|
||||
{{ foo|bar|baz }}
|
||||
"""
|
||||
|
||||
def __init__(self, node, filters, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.node = node
|
||||
self.filters = filters
|
||||
|
||||
def get_items(self):
|
||||
result = [self.node]
|
||||
for filter, args in self.filters:
|
||||
result.append(filter)
|
||||
result.extend(args)
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
return 'FilterExpression(%r, %r)' % (
|
||||
self.node,
|
||||
self.filters
|
||||
)
|
||||
|
||||
|
||||
class TestExpression(Expression):
|
||||
"""
|
||||
{{ foo is lower }}
|
||||
"""
|
||||
|
||||
def __init__(self, node, name, args, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.node = node
|
||||
self.name = name
|
||||
self.args = args
|
||||
|
||||
def get_items(self):
|
||||
return [self.node, self.name] + list(self.args)
|
||||
|
||||
def __repr__(self):
|
||||
return 'TestExpression(%r, %r, %r)' % (
|
||||
self.node,
|
||||
self.name,
|
||||
self.args
|
||||
)
|
||||
|
||||
|
||||
class CallExpression(Expression):
|
||||
"""
|
||||
{{ foo(bar) }}
|
||||
"""
|
||||
|
||||
def __init__(self, node, args, kwargs, dyn_args, dyn_kwargs,
|
||||
lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.node = node
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.dyn_args = dyn_args
|
||||
self.dyn_kwargs = dyn_kwargs
|
||||
|
||||
def get_items(self):
|
||||
return [self.node, self.args, self.kwargs, self.dyn_args,
|
||||
self.dyn_kwargs]
|
||||
|
||||
def __repr__(self):
|
||||
return 'CallExpression(%r, %r, %r, %r, %r)' % (
|
||||
self.node,
|
||||
self.args,
|
||||
self.kwargs,
|
||||
self.dyn_args,
|
||||
self.dyn_kwargs
|
||||
)
|
||||
|
||||
|
||||
class SubscriptExpression(Expression):
|
||||
"""
|
||||
{{ foo.bar }} and {{ foo['bar'] }} etc.
|
||||
"""
|
||||
|
||||
def __init__(self, node, arg, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.node = node
|
||||
self.arg = arg
|
||||
|
||||
def get_items(self):
|
||||
return [self.node, self.arg]
|
||||
|
||||
def __repr__(self):
|
||||
return 'SubscriptExpression(%r, %r)' % (
|
||||
self.node,
|
||||
self.arg
|
||||
)
|
||||
|
||||
|
||||
class SliceExpression(Expression):
|
||||
"""
|
||||
1:2:3 etc.
|
||||
"""
|
||||
|
||||
def __init__(self, start, stop, step, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
self.step = step
|
||||
|
||||
def get_items(self):
|
||||
return [self.start, self.stop, self.step]
|
||||
|
||||
def __repr__(self):
|
||||
return 'SliceExpression(%r, %r, %r)' % (
|
||||
self.start,
|
||||
self.stop,
|
||||
self.step
|
||||
)
|
||||
|
||||
|
||||
class TupleExpression(Expression):
|
||||
"""
|
||||
For loop unpacking and some other things like multiple arguments
|
||||
for subscripts.
|
||||
"""
|
||||
|
||||
def __init__(self, items, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.items = items
|
||||
|
||||
def get_items(self):
|
||||
return list(self.items)
|
||||
|
||||
def allows_assignments(self):
|
||||
for item in self.items:
|
||||
if not item.allows_assignments():
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return 'TupleExpression(%r)' % (self.items,)
|
||||
|
||||
|
||||
class ConcatExpression(Expression):
|
||||
"""
|
||||
For {{ foo ~ bar }}. Because of various reasons (especially because
|
||||
unicode conversion takes place for the left and right expression and
|
||||
is better optimized that way)
|
||||
"""
|
||||
|
||||
def __init__(self, args, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.args = args
|
||||
|
||||
def get_items(self):
|
||||
return list(self.args)
|
||||
|
||||
def __repr__(self):
|
||||
return 'ConcatExpression(%r)' % (self.items,)
|
||||
|
||||
|
||||
class CompareExpression(Expression):
|
||||
"""
|
||||
{{ foo == bar }}, {{ foo >= bar }} etc.
|
||||
"""
|
||||
|
||||
def __init__(self, expr, ops, lineno=None, filename=None):
|
||||
Expression.__init__(self, lineno, filename)
|
||||
self.expr = expr
|
||||
self.ops = ops
|
||||
|
||||
def get_items(self):
|
||||
return [self.expr] + list(chain(*self.ops))
|
||||
|
||||
def __repr__(self):
|
||||
return 'CompareExpression(%r, %r)' % (
|
||||
self.expr,
|
||||
self.ops
|
||||
)
|
||||
|
||||
|
||||
class MulExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo * bar }}
|
||||
"""
|
||||
|
||||
|
||||
class DivExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo / bar }}
|
||||
"""
|
||||
|
||||
|
||||
class FloorDivExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo // bar }}
|
||||
"""
|
||||
|
||||
|
||||
class AddExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo + bar }}
|
||||
"""
|
||||
|
||||
|
||||
class SubExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo - bar }}
|
||||
"""
|
||||
|
||||
|
||||
class ModExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo % bar }}
|
||||
"""
|
||||
|
||||
|
||||
class PowExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo ** bar }}
|
||||
"""
|
||||
|
||||
|
||||
class AndExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo and bar }}
|
||||
"""
|
||||
|
||||
|
||||
class OrExpression(BinaryExpression):
|
||||
"""
|
||||
{{ foo or bar }}
|
||||
"""
|
||||
|
||||
|
||||
class NotExpression(UnaryExpression):
|
||||
"""
|
||||
{{ not foo }}
|
||||
"""
|
||||
|
||||
|
||||
class NegExpression(UnaryExpression):
|
||||
"""
|
||||
{{ -foo }}
|
||||
"""
|
||||
|
||||
|
||||
class PosExpression(UnaryExpression):
|
||||
"""
|
||||
{{ +foo }}
|
||||
"""
|
1187
jinja/parser.py
1187
jinja/parser.py
File diff suppressed because it is too large
Load Diff
166
jinja/plugin.py
166
jinja/plugin.py
@ -1,166 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.plugin
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Support for the `GeneralTemplateInterface`__ and the Buffet interface.
|
||||
|
||||
Do not use this module on your own. We don't recommend those interfaces!
|
||||
If you are able to, you should really use Jinja without those abstraction
|
||||
layers.
|
||||
|
||||
__ http://trac.pocoo.org/wiki/GeneralTemplateInterface
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from warnings import warn
|
||||
from jinja.environment import Environment
|
||||
from jinja.loaders import FunctionLoader, FileSystemLoader, PackageLoader
|
||||
from jinja.exceptions import TemplateNotFound
|
||||
|
||||
|
||||
class BuffetPlugin(object):
|
||||
"""
|
||||
Implements the Jinja buffet plugin. Well. It works for pylons and should
|
||||
work for TurboGears too if their plugin system would work.
|
||||
"""
|
||||
|
||||
def __init__(self, extra_vars_func=None, options=None):
|
||||
if 'jinja.environment' in options:
|
||||
self.env = options['jinja.environment']
|
||||
else:
|
||||
opt = {}
|
||||
for key, value in options.iteritems():
|
||||
if key.startswith('jinja.') and key != 'jinja.extension':
|
||||
opt[key[6:]] = value
|
||||
loader_func = opt.pop('loader_func', None)
|
||||
getmtime_func = opt.pop('getmtime_func', None)
|
||||
use_memcache = opt.pop('use_memcache', False)
|
||||
memcache_size = opt.pop('memcache_size', 40)
|
||||
cache_folder = opt.pop('cache_folder', None)
|
||||
auto_reload = opt.pop('auto_reload', True)
|
||||
if 'searchpath' in opt:
|
||||
opt['loader'] = FileSystemLoader(opt.pop('searchpath'),
|
||||
use_memcache, memcache_size,
|
||||
cache_folder, auto_reload)
|
||||
elif 'package' in opt:
|
||||
opt['loader'] = PackageLoader(opt.pop('package'),
|
||||
opt.pop('package_path', ''),
|
||||
use_memcache, memcache_size,
|
||||
cache_folder, auto_reload)
|
||||
elif loader_func is not None:
|
||||
opt['loader'] = FunctionLoader(loader_func, getmtime_func,
|
||||
use_memcache, memcache_size,
|
||||
cache_folder, auto_reload)
|
||||
self.env = Environment(**opt)
|
||||
|
||||
self.extra_vars_func = extra_vars_func
|
||||
self.extension = options.pop('jinja.extension', 'html')
|
||||
|
||||
def load_template(self, templatename, template_string=None):
|
||||
if template_string is not None:
|
||||
return self.env.from_string(template_string)
|
||||
if templatename.startswith('!'):
|
||||
jinja_name = templatename[1:]
|
||||
else:
|
||||
jinja_name = templatename.replace('.', '/') + '.' + self.extension
|
||||
return self.env.get_template(jinja_name)
|
||||
|
||||
def render(self, info, format='html', fragment=False, template=None):
|
||||
if isinstance(template, basestring):
|
||||
template = self.load_template(template)
|
||||
if self.extra_vars_func:
|
||||
info.update(self.extra_vars_func())
|
||||
return template.render(info)
|
||||
|
||||
|
||||
def jinja_plugin_factory(options):
|
||||
"""
|
||||
Basic implementation of the `GeneralTemplateInterface`.
|
||||
|
||||
Supports ``loader_func`` and ``getmtime_func``, as well as
|
||||
string and file loading but ignores ``mode`` since it's a
|
||||
text based template engine.
|
||||
|
||||
All options passed to this function are forwarded to the
|
||||
jinja environment. Exceptions are the following keys:
|
||||
|
||||
=================== =================================================
|
||||
``environment`` If this is provided it must be the only
|
||||
configuration value and it's used as jinja
|
||||
environment.
|
||||
``searchpath`` If provided a new file system loader with this
|
||||
search path is instanciated.
|
||||
``package`` Name of the python package containing the
|
||||
templates. If this and ``package_path`` is
|
||||
defined a `PackageLoader` is used.
|
||||
``package_path`` Path to the templates inside of a package.
|
||||
``loader_func`` Function that takes the name of the template to
|
||||
load. If it returns a string or unicode object
|
||||
it's used to load a template. If the return
|
||||
value is None it's considered missing.
|
||||
``getmtime_func`` Function used to check if templates requires
|
||||
reloading. Has to return the UNIX timestamp of
|
||||
the last template change or 0 if this template
|
||||
does not exist or requires updates at any cost.
|
||||
``use_memcache`` Set this to ``True`` to enable memory caching.
|
||||
This is usually a good idea in production mode,
|
||||
but disable it during development since it won't
|
||||
reload template changes automatically.
|
||||
This only works in persistent environments like
|
||||
FastCGI.
|
||||
``memcache_size`` Number of template instance you want to cache.
|
||||
Defaults to ``40``.
|
||||
``cache_folder`` Set this to an existing directory to enable
|
||||
caching of templates on the file system. Note
|
||||
that this only affects templates transformed
|
||||
into python code. Default is ``None`` which means
|
||||
that caching is disabled.
|
||||
``auto_reload`` Set this to `False` for a slightly better
|
||||
performance. In that case of `getmtime_func`
|
||||
not being provided this won't have an effect.
|
||||
=================== =================================================
|
||||
"""
|
||||
warn(DeprecationWarning('general plugin interface implementation '
|
||||
'deprecated because not an accepted '
|
||||
'standard.'))
|
||||
|
||||
if 'environment' in options:
|
||||
env = options['environment']
|
||||
if not len(options) == 1:
|
||||
raise TypeError('if environment provided no other '
|
||||
'arguments are allowed')
|
||||
else:
|
||||
loader_func = options.pop('loader_func', None)
|
||||
getmtime_func = options.pop('getmtime_func', None)
|
||||
use_memcache = options.pop('use_memcache', False)
|
||||
memcache_size = options.pop('memcache_size', 40)
|
||||
cache_folder = options.pop('cache_folder', None)
|
||||
auto_reload = options.pop('auto_reload', True)
|
||||
if 'searchpath' in options:
|
||||
options['loader'] = FileSystemLoader(options.pop('searchpath'),
|
||||
use_memcache, memcache_size,
|
||||
cache_folder, auto_reload)
|
||||
elif 'package' in options:
|
||||
options['loader'] = PackageLoader(options.pop('package'),
|
||||
options.pop('package_path', ''),
|
||||
use_memcache, memcache_size,
|
||||
cache_folder, auto_reload)
|
||||
elif loader_func is not None:
|
||||
options['loader'] = FunctionLoader(loader_func, getmtime_func,
|
||||
use_memcache, memcache_size,
|
||||
cache_folder, auto_reload)
|
||||
env = Environment(**options)
|
||||
|
||||
def render_function(template, values, options):
|
||||
if options.get('is_string'):
|
||||
tmpl = env.from_string(template)
|
||||
else:
|
||||
try:
|
||||
tmpl = env.get_template(template)
|
||||
except TemplateNotFound:
|
||||
return
|
||||
return tmpl.render(**values)
|
||||
|
||||
return render_function
|
645
jinja/utils.py
645
jinja/utils.py
@ -1,645 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.utils
|
||||
~~~~~~~~~~~
|
||||
|
||||
Utility functions.
|
||||
|
||||
**license information**: some of the regular expressions and
|
||||
the ``urlize`` function were taken from the django framework.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher, Lawrence Journal-World.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import string
|
||||
from types import MethodType, FunctionType
|
||||
from jinja import nodes
|
||||
from jinja.exceptions import SecurityException, TemplateNotFound
|
||||
from jinja.datastructure import TemplateData
|
||||
|
||||
# the python2.4 version of deque is missing the remove method
|
||||
# because a for loop with a lookup for the missing value written
|
||||
# in python is slower we just use deque if we have python2.5 or higher
|
||||
try:
|
||||
from collections import deque
|
||||
deque.remove
|
||||
except (ImportError, AttributeError):
|
||||
class deque(list):
|
||||
"""
|
||||
Minimal subclass of list that provides the deque
|
||||
interface used by the native `BaseContext` and the
|
||||
`CacheDict`
|
||||
"""
|
||||
def appendleft(self, item):
|
||||
list.insert(self, 0, item)
|
||||
def popleft(self):
|
||||
return list.pop(self, 0)
|
||||
def clear(self):
|
||||
del self[:]
|
||||
|
||||
# support for a working reversed() in 2.3
|
||||
try:
|
||||
reversed = reversed
|
||||
except NameError:
|
||||
def reversed(iterable):
|
||||
if hasattr(iterable, '__reversed__'):
|
||||
return iterable.__reversed__()
|
||||
try:
|
||||
return iter(iterable[::-1])
|
||||
except TypeError:
|
||||
return iter(tuple(iterable)[::-1])
|
||||
|
||||
# set support for python 2.3
|
||||
try:
|
||||
set = set
|
||||
except NameError:
|
||||
from sets import Set as set
|
||||
|
||||
# sorted support (just a simplified version)
|
||||
try:
|
||||
sorted = sorted
|
||||
except NameError:
|
||||
_cmp = cmp
|
||||
def sorted(seq, cmp=None, key=None, reverse=False):
|
||||
rv = list(seq)
|
||||
if key is not None:
|
||||
cmp = lambda a, b: _cmp(key(a), key(b))
|
||||
rv.sort(cmp)
|
||||
if reverse:
|
||||
rv.reverse()
|
||||
return rv
|
||||
|
||||
# group by support
|
||||
try:
|
||||
from itertools import groupby
|
||||
except ImportError:
|
||||
class groupby(object):
|
||||
|
||||
def __init__(self, iterable, key=lambda x: x):
|
||||
self.keyfunc = key
|
||||
self.it = iter(iterable)
|
||||
self.tgtkey = self.currkey = self.currvalue = xrange(0)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
while self.currkey == self.tgtkey:
|
||||
self.currvalue = self.it.next()
|
||||
self.currkey = self.keyfunc(self.currvalue)
|
||||
self.tgtkey = self.currkey
|
||||
return (self.currkey, self._grouper(self.tgtkey))
|
||||
|
||||
def _grouper(self, tgtkey):
|
||||
while self.currkey == tgtkey:
|
||||
yield self.currvalue
|
||||
self.currvalue = self.it.next()
|
||||
self.currkey = self.keyfunc(self.currvalue)
|
||||
|
||||
#: function types
|
||||
callable_types = (FunctionType, MethodType)
|
||||
|
||||
#: number of maximal range items
|
||||
MAX_RANGE = 1000000
|
||||
|
||||
_word_split_re = re.compile(r'(\s+)')
|
||||
|
||||
_punctuation_re = re.compile(
|
||||
'^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
|
||||
'|'.join([re.escape(p) for p in ('(', '<', '<')]),
|
||||
'|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '>')])
|
||||
)
|
||||
)
|
||||
|
||||
_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
|
||||
|
||||
#: used by from_string as cache
|
||||
_from_string_env = None
|
||||
|
||||
|
||||
def escape(s, quote=None):
|
||||
"""
|
||||
SGML/XML escape an unicode object.
|
||||
"""
|
||||
s = s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
if not quote:
|
||||
return s
|
||||
return s.replace('"', """)
|
||||
|
||||
|
||||
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)
|
||||
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
|
||||
len(middle) > 0 and
|
||||
middle[0] in string.letters + string.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 from_string(source):
|
||||
"""
|
||||
Create a template from the template source.
|
||||
"""
|
||||
global _from_string_env
|
||||
if _from_string_env is None:
|
||||
from jinja.environment import Environment
|
||||
_from_string_env = Environment()
|
||||
return _from_string_env.from_string(source)
|
||||
|
||||
|
||||
#: minor speedup
|
||||
_getattr = getattr
|
||||
|
||||
def get_attribute(obj, name):
|
||||
"""
|
||||
Return the attribute from name. Raise either `AttributeError`
|
||||
or `SecurityException` if something goes wrong.
|
||||
"""
|
||||
if not isinstance(name, basestring):
|
||||
raise AttributeError(name)
|
||||
if name[:2] == name[-2:] == '__':
|
||||
raise SecurityException('not allowed to access internal attributes')
|
||||
if getattr(obj, '__class__', None) in callable_types and \
|
||||
name.startswith('func_') or name.startswith('im_'):
|
||||
raise SecurityException('not allowed to access function attributes')
|
||||
|
||||
r = _getattr(obj, 'jinja_allowed_attributes', None)
|
||||
if r is not None and name not in r:
|
||||
raise SecurityException('disallowed attribute accessed')
|
||||
|
||||
# attribute lookups convert unicode strings to ascii bytestrings.
|
||||
# this process could raise an UnicodeEncodeError.
|
||||
try:
|
||||
return _getattr(obj, name)
|
||||
except UnicodeError:
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
def safe_range(start, stop=None, step=None):
|
||||
"""
|
||||
"Safe" form of range that does not generate too large lists.
|
||||
"""
|
||||
if step is None:
|
||||
step = 1
|
||||
if stop is None:
|
||||
r = xrange(0, start, step)
|
||||
else:
|
||||
r = xrange(start, stop, step)
|
||||
if len(r) > MAX_RANGE:
|
||||
def limit():
|
||||
i = 0
|
||||
for item in r:
|
||||
i += 1
|
||||
yield item
|
||||
if i >= MAX_RANGE:
|
||||
break
|
||||
return list(limit())
|
||||
return list(r)
|
||||
|
||||
|
||||
def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
|
||||
"""
|
||||
Generate some lorem impsum for the template.
|
||||
"""
|
||||
from jinja.constants import LOREM_IPSUM_WORDS
|
||||
from random import choice, random, randrange
|
||||
words = LOREM_IPSUM_WORDS.split()
|
||||
result = []
|
||||
|
||||
for _ in xrange(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(xrange(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 u'\n'.join([u'<p>%s</p>' % escape(x) for x in result])
|
||||
|
||||
|
||||
def watch_changes(env, context, iterable, *attributes):
|
||||
"""
|
||||
Wise replacement for ``{% ifchanged %}``.
|
||||
"""
|
||||
# find the attributes to watch
|
||||
if attributes:
|
||||
tests = []
|
||||
tmp = []
|
||||
for attribute in attributes:
|
||||
if isinstance(attribute, (str, unicode, int, long, bool)):
|
||||
tmp.append(attribute)
|
||||
else:
|
||||
tests.append(tuple(attribute))
|
||||
if tmp:
|
||||
tests.append(tuple(attribute))
|
||||
last = tuple([object() for x in tests])
|
||||
# or no attributes if we watch the object itself
|
||||
else:
|
||||
tests = None
|
||||
last = object()
|
||||
|
||||
# iterate trough it and keep check the attributes or values
|
||||
for item in iterable:
|
||||
if tests is None:
|
||||
cur = item
|
||||
else:
|
||||
cur = tuple([env.get_attributes(item, x) for x in tests])
|
||||
if cur != last:
|
||||
changed = True
|
||||
last = cur
|
||||
else:
|
||||
changed = False
|
||||
yield changed, item
|
||||
watch_changes.jinja_context_callable = True
|
||||
|
||||
|
||||
def render_included(env, context, template_name):
|
||||
"""
|
||||
Works like djangos {% include %} tag. It doesn't include the
|
||||
template but load it independently and renders it to a string.
|
||||
"""
|
||||
#XXX: ignores parent completely!
|
||||
tmpl = env.get_template(template_name)
|
||||
return tmpl.render(context.to_dict())
|
||||
render_included.jinja_context_callable = True
|
||||
|
||||
|
||||
# python2.4 and lower has a bug regarding joining of broken generators.
|
||||
# because of the runtime debugging system we have to keep track of the
|
||||
# number of frames to skip. that's what RUNTIME_EXCEPTION_OFFSET is for.
|
||||
try:
|
||||
_test_singleton = object()
|
||||
def _test_gen_bug():
|
||||
raise TypeError(_test_singleton)
|
||||
yield None
|
||||
''.join(_test_gen_bug())
|
||||
except TypeError, e:
|
||||
if e.args and e.args[0] is _test_singleton:
|
||||
capture_generator = u''.join
|
||||
RUNTIME_EXCEPTION_OFFSET = 1
|
||||
else:
|
||||
capture_generator = lambda gen: u''.join(tuple(gen))
|
||||
RUNTIME_EXCEPTION_OFFSET = 2
|
||||
del _test_singleton, _test_gen_bug
|
||||
|
||||
|
||||
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 buffereater(f, template_data=False):
|
||||
"""
|
||||
Used by the python translator to capture output of substreams.
|
||||
(macros, filter sections etc)
|
||||
"""
|
||||
def wrapped(*a, **kw):
|
||||
__traceback_hide__ = True
|
||||
rv = capture_generator(f(*a, **kw))
|
||||
if template_data:
|
||||
rv = TemplateData(rv)
|
||||
return rv
|
||||
return wrapped
|
||||
|
||||
|
||||
def empty_block(context):
|
||||
"""
|
||||
An empty callable that just returns an empty decorator.
|
||||
Used to represent empty blocks.
|
||||
"""
|
||||
if 0: yield None
|
||||
|
||||
|
||||
def collect_translations(ast):
|
||||
"""
|
||||
Collect all translatable strings for the given ast. The
|
||||
return value is a list of tuples in the form ``(lineno, singular,
|
||||
plural)``. If a translation doesn't require a plural form the
|
||||
third item is `None`.
|
||||
"""
|
||||
todo = [ast]
|
||||
result = []
|
||||
while todo:
|
||||
node = todo.pop()
|
||||
if node.__class__ is nodes.Trans:
|
||||
result.append((node.lineno, node.singular, node.plural))
|
||||
elif node.__class__ is nodes.CallExpression and \
|
||||
node.node.__class__ is nodes.NameExpression and \
|
||||
node.node.name == '_':
|
||||
if len(node.args) == 1 and not node.kwargs and not node.dyn_args \
|
||||
and not node.dyn_kwargs and \
|
||||
node.args[0].__class__ is nodes.ConstantExpression:
|
||||
result.append((node.lineno, node.args[0].value, None))
|
||||
todo.extend(node.get_child_nodes())
|
||||
result.sort(lambda a, b: cmp(a[0], b[0]))
|
||||
return result
|
||||
|
||||
|
||||
class DebugHelper(object):
|
||||
"""
|
||||
Debugging Helper. Available in the template as "debug".
|
||||
"""
|
||||
jinja_context_callable = True
|
||||
jinja_allowed_attributes = ['filters']
|
||||
|
||||
def __init__(self):
|
||||
raise TypeError('cannot create %r instances' %
|
||||
self.__class__.__name__)
|
||||
|
||||
def __call__(self, env, context):
|
||||
"""Print a nice representation of the context."""
|
||||
return pformat(context.to_dict(), verbose=True)
|
||||
|
||||
def filters(self, env, context, builtins=True):
|
||||
"""List the filters."""
|
||||
from inspect import getdoc
|
||||
strip = set()
|
||||
if not builtins:
|
||||
from jinja.defaults import DEFAULT_FILTERS
|
||||
strip = set(DEFAULT_FILTERS.values())
|
||||
filters = env.filters.items()
|
||||
filters.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
|
||||
result = []
|
||||
for name, f in filters:
|
||||
if f in strip:
|
||||
continue
|
||||
doc = '\n'.join([' ' + x for x in (getdoc(f) or '').splitlines()])
|
||||
result.append('`%s`\n\n%s' % (name, doc))
|
||||
return '\n\n'.join(result)
|
||||
filters.jinja_context_callable = True
|
||||
|
||||
def tests(self, env, context, builtins=True):
|
||||
"""List the tests."""
|
||||
from inspect import getdoc
|
||||
strip = set()
|
||||
if not builtins:
|
||||
from jinja.defaults import DEFAULT_TESTS
|
||||
strip = set(DEFAULT_TESTS.values())
|
||||
tests = env.tests.items()
|
||||
tests.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
|
||||
result = []
|
||||
for name, f in tests:
|
||||
if f in strip:
|
||||
continue
|
||||
doc = '\n'.join([' ' + x for x in (getdoc(f) or '').splitlines()])
|
||||
result.append('`%s`\n\n%s' % (name, doc))
|
||||
return '\n\n'.join(result)
|
||||
tests.jinja_context_callable = True
|
||||
|
||||
def __str__(self):
|
||||
print 'use debug() for debugging the context'
|
||||
|
||||
|
||||
#: the singleton instance of `DebugHelper`
|
||||
debug_helper = object.__new__(DebugHelper)
|
||||
|
||||
|
||||
class CacheDict(object):
|
||||
"""
|
||||
A dict like object that stores a limited number of items and forgets
|
||||
about the least recently used items::
|
||||
|
||||
>>> cache = CacheDict(3)
|
||||
>>> cache['A'] = 0
|
||||
>>> cache['B'] = 1
|
||||
>>> cache['C'] = 2
|
||||
>>> len(cache)
|
||||
3
|
||||
|
||||
If we now access 'A' again it has a higher priority than B::
|
||||
|
||||
>>> cache['A']
|
||||
0
|
||||
|
||||
If we add a new item 'D' now 'B' will disappear::
|
||||
|
||||
>>> cache['D'] = 3
|
||||
>>> len(cache)
|
||||
3
|
||||
>>> 'B' in cache
|
||||
False
|
||||
|
||||
If you iterate over the object the most recently used item will be
|
||||
yielded First::
|
||||
|
||||
>>> for item in cache:
|
||||
... print item
|
||||
D
|
||||
A
|
||||
C
|
||||
|
||||
If you want to iterate the other way round use ``reverse(cache)``.
|
||||
|
||||
Implementation note: This is not a nice way to solve that problem but
|
||||
for smaller capacities it's faster than a linked list.
|
||||
Perfect for template environments where you don't expect too many
|
||||
different keys.
|
||||
"""
|
||||
|
||||
def __init__(self, capacity):
|
||||
self.capacity = capacity
|
||||
self._mapping = {}
|
||||
self._queue = deque()
|
||||
|
||||
# alias all queue methods for faster lookup
|
||||
self._popleft = self._queue.popleft
|
||||
self._pop = self._queue.pop
|
||||
self._remove = self._queue.remove
|
||||
self._append = self._queue.append
|
||||
|
||||
def copy(self):
|
||||
"""
|
||||
Return an shallow copy of the instance.
|
||||
"""
|
||||
rv = CacheDict(self.capacity)
|
||||
rv._mapping.update(self._mapping)
|
||||
rv._queue = self._queue[:]
|
||||
return rv
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""
|
||||
Return an item from the cache dict or `default`
|
||||
"""
|
||||
if key in self:
|
||||
return self[key]
|
||||
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.
|
||||
"""
|
||||
if key in self:
|
||||
return self[key]
|
||||
self[key] = default
|
||||
return default
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear the cache dict.
|
||||
"""
|
||||
self._mapping.clear()
|
||||
self._queue.clear()
|
||||
|
||||
def __contains__(self, key):
|
||||
"""
|
||||
Check if a key exists in this cache dict.
|
||||
"""
|
||||
return key in self._mapping
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Return the current size of the cache dict.
|
||||
"""
|
||||
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 dict. Moves the item up so that
|
||||
it has the highest priority then.
|
||||
|
||||
Raise an `KeyError` if it does not exist.
|
||||
"""
|
||||
rv = self._mapping[key]
|
||||
if self._queue[-1] != key:
|
||||
self._remove(key)
|
||||
self._append(key)
|
||||
return rv
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""
|
||||
Sets the value for an item. Moves the item up so that it
|
||||
has the highest priority then.
|
||||
"""
|
||||
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
|
||||
|
||||
def __delitem__(self, key):
|
||||
"""
|
||||
Remove an item from the cache dict.
|
||||
Raise an `KeyError` if it does not exist.
|
||||
"""
|
||||
del self._mapping[key]
|
||||
self._remove(key)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterate over all values in the cache dict, ordered by
|
||||
the most recent usage.
|
||||
"""
|
||||
return reversed(self._queue)
|
||||
|
||||
def __reversed__(self):
|
||||
"""
|
||||
Iterate over the values in the cache dict, oldest items
|
||||
coming first.
|
||||
"""
|
||||
return iter(self._queue)
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def __deepcopy__(self):
|
||||
"""
|
||||
Return a deep copy of the cache dict.
|
||||
"""
|
||||
from copy import deepcopy
|
||||
rv = CacheDict(self.capacity)
|
||||
rv._mapping = deepcopy(self._mapping)
|
||||
rv._queue = deepcopy(self._queue)
|
||||
return rv
|
||||
|
||||
|
||||
NAMESPACE = {
|
||||
'range': safe_range,
|
||||
'debug': debug_helper,
|
||||
'lipsum': generate_lorem_ipsum,
|
||||
'watchchanges': watch_changes,
|
||||
'rendertemplate': render_included
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja
|
||||
~~~~~
|
||||
jinja2
|
||||
~~~~~~
|
||||
|
||||
Jinja is a `sandboxed`_ template engine written in pure Python. It
|
||||
provides a `Django`_ like non-XML syntax and compiles templates into
|
||||
@ -53,24 +53,6 @@
|
||||
.. _Jinja tip: http://dev.pocoo.org/hg/jinja-main/archive/tip.tar.gz#egg=Jinja-dev
|
||||
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:copyright: 2008 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from jinja.environment import Environment
|
||||
from jinja.datastructure import Markup
|
||||
from jinja.plugin import jinja_plugin_factory as template_plugin_factory
|
||||
from jinja.loaders import FileSystemLoader, PackageLoader, DictLoader, \
|
||||
ChoiceLoader, FunctionLoader, MemcachedFileSystemLoader
|
||||
from jinja.utils import from_string
|
||||
|
||||
|
||||
__all__ = ['Environment', 'Markup', 'FileSystemLoader', 'PackageLoader',
|
||||
'DictLoader', 'ChoiceLoader', 'FunctionLoader',
|
||||
'MemcachedFileSystemLoader', 'from_string']
|
||||
|
||||
__version__ = '1.3'
|
||||
__author__ = 'Armin Ronacher'
|
||||
__url__ = 'http://jinja.pocoo.org/'
|
||||
__license__ = 'BSD'
|
||||
__docformat__ = 'restructuredtext'
|
137
jinja2/datastructure.py
Normal file
137
jinja2/datastructure.py
Normal file
@ -0,0 +1,137 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.datastructure
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Module that helds several data types used in the template engine.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from operator import itemgetter
|
||||
from jinja.exceptions import TemplateSyntaxError, TemplateRuntimeError
|
||||
|
||||
|
||||
_missing = object()
|
||||
|
||||
|
||||
class Token(tuple):
|
||||
"""
|
||||
Token class.
|
||||
"""
|
||||
__slots__ = ()
|
||||
lineno, type, value = map(itemgetter, range(3))
|
||||
|
||||
def __new__(cls, lineno, type, value):
|
||||
return tuple.__new__(cls, (lineno, type, value))
|
||||
|
||||
def __str__(self):
|
||||
from jinja.lexer import keywords, reverse_operators
|
||||
if self.type in keywords:
|
||||
return self.type
|
||||
elif self.type in reverse_operators:
|
||||
return reverse_operators[self.type]
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
return 'Token(%r, %r, %r)' % (
|
||||
self.lineno,
|
||||
self.type,
|
||||
self.value
|
||||
)
|
||||
|
||||
|
||||
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 == 'eof':
|
||||
self._stream.close()
|
||||
raise StopIteration()
|
||||
self._stream.next()
|
||||
return token
|
||||
|
||||
|
||||
class TokenStream(object):
|
||||
"""
|
||||
A token stream wraps a generator and supports pushing tokens back.
|
||||
It also provides some functions to expect tokens and similar stuff.
|
||||
|
||||
Important note: Do never push more than one token back to the
|
||||
stream. Although the stream object won't stop you
|
||||
from doing so, the behavior is undefined. Multiple
|
||||
pushed tokens are only used internally!
|
||||
"""
|
||||
|
||||
def __init__(self, generator, filename):
|
||||
self._next = generator.next
|
||||
self._pushed = []
|
||||
self.current = Token(1, 'initial', '')
|
||||
self.filename = filename
|
||||
self.next()
|
||||
|
||||
def __iter__(self):
|
||||
return TokenStreamIterator(self)
|
||||
|
||||
def lineno(self):
|
||||
"""The current line number."""
|
||||
return self.current.lineno
|
||||
lineno = property(lineno, doc=lineno.__doc__)
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Are we at the end of the tokenstream?"""
|
||||
return bool(self._pushed) or self.current.type != 'eof'
|
||||
|
||||
eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
|
||||
|
||||
def push(self, token):
|
||||
"""Push a token back to the stream."""
|
||||
self._pushed.append(token)
|
||||
|
||||
def skip(self, n):
|
||||
"""Got n tokens ahead."""
|
||||
for x in xrange(n):
|
||||
self.next()
|
||||
|
||||
def next(self):
|
||||
"""Go one token ahead."""
|
||||
if self._pushed:
|
||||
self.current = self._pushed.pop()
|
||||
elif self.current.type != 'eof':
|
||||
try:
|
||||
self.current = self._next()
|
||||
except StopIteration:
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""Close the stream."""
|
||||
self.current = Token(self.current.lineno, 'eof', '')
|
||||
self._next = None
|
||||
|
||||
def expect(self, token_type, token_value=_missing):
|
||||
"""Expect a given token type and return it"""
|
||||
if self.current.type != token_type:
|
||||
raise TemplateSyntaxError("expected token %r, got %r" %
|
||||
(token_type, self.current.type),
|
||||
self.current.lineno,
|
||||
self.filename)
|
||||
elif token_value is not _missing and \
|
||||
self.current.value != token_value:
|
||||
raise TemplateSyntaxError("expected %r, got %r" %
|
||||
(token_value, self.current.value),
|
||||
self.current.lineno,
|
||||
self.filename)
|
||||
try:
|
||||
return self.current
|
||||
finally:
|
||||
self.next()
|
@ -10,7 +10,7 @@
|
||||
"""
|
||||
from jinja.filters import FILTERS as DEFAULT_FILTERS
|
||||
from jinja.tests import TESTS as DEFAULT_TESTS
|
||||
from jinja.utils import NAMESPACE as DEFAULT_NAMESPACE
|
||||
DEFAULT_NAMESPACE = {}
|
||||
|
||||
|
||||
__all__ = ['DEFAULT_FILTERS', 'DEFAULT_TESTS', 'DEFAULT_NAMESPACE']
|
115
jinja2/environment.py
Normal file
115
jinja2/environment.py
Normal file
@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.environment
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides a class that holds runtime and parsing time options.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja.lexer import Lexer
|
||||
from jinja.parser import Parser
|
||||
from jinja.loaders import LoaderWrapper
|
||||
from jinja.datastructure import SilentUndefined, Markup, Context, FakeTranslator
|
||||
from jinja.utils import collect_translations, get_attribute
|
||||
from jinja.exceptions import FilterNotFound, TestNotFound, \
|
||||
SecurityException, TemplateSyntaxError
|
||||
from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
|
||||
|
||||
|
||||
__all__ = ['Environment']
|
||||
|
||||
|
||||
#: minor speedup
|
||||
_getattr = getattr
|
||||
|
||||
|
||||
class Environment(object):
|
||||
"""
|
||||
The Jinja environment.
|
||||
|
||||
The core component of Jinja is the `Environment`. It contains
|
||||
important shared variables like configuration, filters, tests,
|
||||
globals and others.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
block_start_string='{%',
|
||||
block_end_string='%}',
|
||||
variable_start_string='{{',
|
||||
variable_end_string='}}',
|
||||
comment_start_string='{#',
|
||||
comment_end_string='#}',
|
||||
trim_blocks=False,
|
||||
loader=None):
|
||||
"""
|
||||
Here the possible initialization parameters:
|
||||
|
||||
========================= ============================================
|
||||
`block_start_string` the string marking the begin of a block.
|
||||
this defaults to ``'{%'``.
|
||||
`block_end_string` the string marking the end of a block.
|
||||
defaults to ``'%}'``.
|
||||
`variable_start_string` the string marking the begin of a print
|
||||
statement. defaults to ``'{{'``.
|
||||
`comment_start_string` the string marking the begin of a
|
||||
comment. defaults to ``'{#'``.
|
||||
`comment_end_string` the string marking the end of a comment.
|
||||
defaults to ``'#}'``.
|
||||
`trim_blocks` If this is set to ``True`` the first newline
|
||||
after a block is removed (block, not
|
||||
variable tag!). Defaults to ``False``.
|
||||
`loader` The loader for this environment.
|
||||
========================= ============================================
|
||||
"""
|
||||
|
||||
# lexer / parser information
|
||||
self.block_start_string = block_start_string
|
||||
self.block_end_string = block_end_string
|
||||
self.variable_start_string = variable_start_string
|
||||
self.variable_end_string = variable_end_string
|
||||
self.comment_start_string = comment_start_string
|
||||
self.comment_end_string = comment_end_string
|
||||
self.trim_blocks = trim_blocks
|
||||
|
||||
# other stuff
|
||||
self.template_charset = template_charset
|
||||
self.loader = loader
|
||||
|
||||
# defaults
|
||||
self.filters = DEFAULT_FILTERS.copy()
|
||||
self.tests = DEFAULT_TESTS.copy()
|
||||
self.globals = DEFAULT_NAMESPACE.copy()
|
||||
|
||||
# create lexer
|
||||
self.lexer = Lexer(self)
|
||||
|
||||
def loader(self, value):
|
||||
"""
|
||||
Get or set the template loader.
|
||||
"""
|
||||
self._loader = LoaderWrapper(self, value)
|
||||
loader = property(lambda s: s._loader, loader, doc=loader.__doc__)
|
||||
|
||||
def parse(self, source, filename=None):
|
||||
"""
|
||||
Parse the sourcecode and return the abstract syntax tree. This tree
|
||||
of nodes is used by the `translators`_ to convert the template into
|
||||
executable source- or bytecode.
|
||||
|
||||
.. _translators: translators.txt
|
||||
"""
|
||||
parser = Parser(self, source, filename)
|
||||
return parser.parse()
|
||||
|
||||
def lex(self, source, filename=None):
|
||||
"""
|
||||
Lex the given sourcecode and return a generator that yields tokens.
|
||||
The stream returned is not usable for Jinja but can be used if
|
||||
Jinja templates should be processed by other tools (for example
|
||||
syntax highlighting etc)
|
||||
|
||||
The tuples are returned in the form ``(lineno, token, value)``.
|
||||
"""
|
||||
return self.lexer.tokeniter(source, filename)
|
42
jinja2/exceptions.py
Normal file
42
jinja2/exceptions.py
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.exceptions
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Jinja exceptions.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
class TemplateError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class TemplateNotFound(IOError, LookupError, TemplateError):
|
||||
"""
|
||||
Raised if a template does not exist.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
IOError.__init__(self, name)
|
||||
self.name = name
|
||||
|
||||
|
||||
class TemplateSyntaxError(SyntaxError, TemplateError):
|
||||
"""
|
||||
Raised to tell the user that there is a problem with the template.
|
||||
"""
|
||||
|
||||
def __init__(self, message, lineno, filename):
|
||||
SyntaxError.__init__(self, message)
|
||||
self.lineno = lineno
|
||||
self.filename = filename
|
||||
|
||||
|
||||
class TemplateRuntimeError(TemplateError):
|
||||
"""
|
||||
Raised by the template engine if a tag encountered an error when
|
||||
rendering.
|
||||
"""
|
@ -15,10 +15,6 @@ try:
|
||||
except ImportError:
|
||||
itemgetter = lambda a: lambda b: b[a]
|
||||
from urllib import urlencode, quote
|
||||
from jinja.utils import urlize, escape, reversed, sorted, groupby, \
|
||||
get_attribute, pformat
|
||||
from jinja.datastructure import TemplateData
|
||||
from jinja.exceptions import FilterArgumentError, SecurityException
|
||||
|
||||
|
||||
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.lexer
|
||||
~~~~~~~~~~~
|
||||
jinja2.lexer
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module implements a Jinja / Python combination lexer. The
|
||||
`Lexer` class provided by this module is used to do some preprocessing
|
||||
@ -11,14 +11,6 @@
|
||||
operators we don't allow in templates. On the other hand it separates
|
||||
template code and python code in expressions.
|
||||
|
||||
Because of some limitations in the compiler package which are just
|
||||
natural but annoying for Jinja, the lexer also "escapes" non names that
|
||||
are not keywords. The Jinja parser then removes those escaping marks
|
||||
again.
|
||||
|
||||
This is required in order to make "class" and some other python keywords
|
||||
we don't use valid identifiers.
|
||||
|
||||
:copyright: 2007-2008 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
@ -26,7 +18,6 @@ import re
|
||||
import unicodedata
|
||||
from jinja.datastructure import TokenStream, Token
|
||||
from jinja.exceptions import TemplateSyntaxError
|
||||
from jinja.utils import set, sorted
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
|
||||
@ -40,20 +31,20 @@ _lexer_cache = WeakValueDictionary()
|
||||
|
||||
# static regular expressions
|
||||
whitespace_re = re.compile(r'\s+(?um)')
|
||||
name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
|
||||
string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?ms)')
|
||||
integer_re = re.compile(r'\d+')
|
||||
name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
|
||||
float_re = re.compile(r'\d+\.\d+')
|
||||
regex_re = re.compile(r'\@/([^/\\]*(?:\\.[^/\\]*)*)*/[a-z]*(?ms)')
|
||||
|
||||
|
||||
# set of used keywords
|
||||
keywords = set(['and', 'block', 'cycle', 'elif', 'else', 'endblock',
|
||||
keywords = set(['and', 'block', 'elif', 'else', 'endblock',
|
||||
'endfilter', 'endfor', 'endif', 'endmacro', 'endraw',
|
||||
'endtrans', 'extends', 'filter', 'for', 'if', 'in',
|
||||
'include', 'is', 'macro', 'not', 'or', 'pluralize', 'raw',
|
||||
'recursive', 'set', 'trans', 'print', 'call', 'endcall'])
|
||||
'recursive', 'set', 'trans', 'call', 'endcall',
|
||||
'true', 'false', 'none'])
|
||||
|
||||
# bind operators to token types
|
||||
operators = {
|
||||
@ -65,8 +56,6 @@ operators = {
|
||||
'%': 'mod',
|
||||
'**': 'pow',
|
||||
'~': 'tilde',
|
||||
'!': 'bang',
|
||||
'@': 'at',
|
||||
'[': 'lbracket',
|
||||
']': 'rbracket',
|
||||
'(': 'lparen',
|
||||
@ -83,7 +72,8 @@ operators = {
|
||||
'.': 'dot',
|
||||
':': 'colon',
|
||||
'|': 'pipe',
|
||||
',': 'comma'
|
||||
',': 'comma',
|
||||
';': 'semicolon'
|
||||
}
|
||||
|
||||
reverse_operators = dict([(v, k) for k, v in operators.iteritems()])
|
||||
@ -242,9 +232,10 @@ class Lexer(object):
|
||||
(whitespace_re, None, None),
|
||||
(float_re, 'float', None),
|
||||
(integer_re, 'integer', None),
|
||||
('%s' % '|'.join(sorted(keywords, key=lambda x: -len(x))),
|
||||
'keyword', None),
|
||||
(name_re, 'name', None),
|
||||
(string_re, 'string', None),
|
||||
(regex_re, 'regex', None),
|
||||
(operator_re, 'operator', None)
|
||||
]
|
||||
|
||||
@ -351,22 +342,16 @@ class Lexer(object):
|
||||
value = str(value)
|
||||
except UnicodeError:
|
||||
pass
|
||||
elif token == 'keyword':
|
||||
token = str(value)
|
||||
elif token == 'name':
|
||||
value = str(value)
|
||||
if value in keywords:
|
||||
token = value
|
||||
value = ''
|
||||
elif token == 'string':
|
||||
value = unescape_string(lineno, filename, value[1:-1])
|
||||
try:
|
||||
value = str(value)
|
||||
except UnicodeError:
|
||||
pass
|
||||
elif token == 'regex':
|
||||
args = value[value.rfind('/') + 1:]
|
||||
value = unescape_regex(value[2:-(len(args) + 1)])
|
||||
if args:
|
||||
value = '(?%s)%s' % (args, value)
|
||||
elif token == 'integer':
|
||||
value = int(value)
|
||||
elif token == 'float':
|
10
jinja2/loaders.py
Normal file
10
jinja2/loaders.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.loaders
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Jinja loader classes.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher, Bryan McLemore.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
494
jinja2/nodes.py
Normal file
494
jinja2/nodes.py
Normal file
@ -0,0 +1,494 @@
|
||||
# -*- 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: 2007 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import operator
|
||||
from itertools import chain, izip
|
||||
from copy import copy
|
||||
|
||||
|
||||
_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
|
||||
}
|
||||
|
||||
|
||||
class Impossible(Exception):
|
||||
"""
|
||||
Raised if the node could not perform a requested action.
|
||||
"""
|
||||
|
||||
|
||||
class NodeType(type):
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
for attr in '_fields', '_attributes':
|
||||
storage = []
|
||||
for base in bases:
|
||||
storage.extend(getattr(base, attr, ()))
|
||||
storage.extend(d.get(attr, ()))
|
||||
assert len(storage) == len(set(storage))
|
||||
d[attr] = tuple(storage)
|
||||
return type.__new__(cls, name, bases, d)
|
||||
|
||||
|
||||
class Node(object):
|
||||
"""
|
||||
Base jinja node.
|
||||
"""
|
||||
__metaclass__ = NodeType
|
||||
_fields = ()
|
||||
_attributes = ('lineno',)
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
if args:
|
||||
if len(args) != 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, args):
|
||||
setattr(self, name, arg)
|
||||
for attr in self._attributes:
|
||||
setattr(self, attr, kw.pop(attr, None))
|
||||
if kw:
|
||||
raise TypeError('unknown keyword argument %r' %
|
||||
iter(kw).next())
|
||||
|
||||
def iter_fields(self):
|
||||
for name in self._fields:
|
||||
try:
|
||||
yield name, getattr(self, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def iter_child_nodes(self):
|
||||
for field, item in self.iter_fields():
|
||||
if isinstance(item, list):
|
||||
for n in item:
|
||||
if isinstance(n, Node):
|
||||
yield n
|
||||
elif isinstance(item, Node):
|
||||
yield item
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
class Helper(Node):
|
||||
"""
|
||||
Nodes that exist in a specific context only.
|
||||
"""
|
||||
|
||||
|
||||
class Template(Node):
|
||||
"""
|
||||
Node that represents a template.
|
||||
"""
|
||||
_fields = ('extends', '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 = ('extends',)
|
||||
|
||||
|
||||
class For(Stmt):
|
||||
"""
|
||||
A node that represents a for loop
|
||||
"""
|
||||
_fields = ('item', 'seq', 'body', 'else_', 'recursive')
|
||||
|
||||
|
||||
class If(Stmt):
|
||||
"""
|
||||
A node that represents an if condition.
|
||||
"""
|
||||
_fields = ('test', 'body', 'else_')
|
||||
|
||||
|
||||
class Macro(Stmt):
|
||||
"""
|
||||
A node that represents a macro.
|
||||
"""
|
||||
_fields = ('name', 'arguments', 'body')
|
||||
|
||||
|
||||
class CallBlock(Stmt):
|
||||
"""
|
||||
A node that represents am extended macro call.
|
||||
"""
|
||||
_fields = ('expr', 'body')
|
||||
|
||||
|
||||
class Set(Stmt):
|
||||
"""
|
||||
Allows defining own variables.
|
||||
"""
|
||||
_fields = ('name', 'expr')
|
||||
|
||||
|
||||
class FilterBlock(Stmt):
|
||||
"""
|
||||
Node for filter sections.
|
||||
"""
|
||||
_fields = ('body', 'filters')
|
||||
|
||||
|
||||
class Block(Stmt):
|
||||
"""
|
||||
A node that represents a block.
|
||||
"""
|
||||
_fields = ('name', 'body')
|
||||
|
||||
|
||||
class Include(Stmt):
|
||||
"""
|
||||
A node that represents the include tag.
|
||||
"""
|
||||
_fields = ('template',)
|
||||
|
||||
|
||||
class Trans(Stmt):
|
||||
"""
|
||||
A node for translatable sections.
|
||||
"""
|
||||
_fields = ('singular', 'plural', 'indicator', 'replacements')
|
||||
|
||||
|
||||
class ExprStmt(Stmt):
|
||||
"""
|
||||
A statement that evaluates an expression to None.
|
||||
"""
|
||||
_fields = ('node',)
|
||||
|
||||
|
||||
class Expr(Node):
|
||||
"""
|
||||
Baseclass for all expressions.
|
||||
"""
|
||||
|
||||
def as_const(self):
|
||||
"""
|
||||
Return the value of the expression as constant or raise `Impossible`
|
||||
if this was not possible.
|
||||
"""
|
||||
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
|
||||
|
||||
def as_const(self):
|
||||
f = _binop_to_func[self.operator]
|
||||
try:
|
||||
return f(self.left.as_const(), self.right.as_const())
|
||||
except:
|
||||
print self.left, f, self.right
|
||||
raise Impossible()
|
||||
|
||||
|
||||
class UnaryExpr(Expr):
|
||||
"""
|
||||
Baseclass for all unary expressions.
|
||||
"""
|
||||
_fields = ('node',)
|
||||
operator = None
|
||||
|
||||
def as_const(self):
|
||||
f = _uaop_to_func[self.operator]
|
||||
try:
|
||||
return f(self.node.as_const())
|
||||
except:
|
||||
raise Impossible()
|
||||
|
||||
|
||||
class Name(Expr):
|
||||
"""
|
||||
any name such as {{ foo }}
|
||||
"""
|
||||
_fields = ('name',)
|
||||
|
||||
def can_assign(self):
|
||||
return True
|
||||
|
||||
|
||||
class Literal(Expr):
|
||||
"""
|
||||
Baseclass for literals.
|
||||
"""
|
||||
|
||||
|
||||
class Const(Literal):
|
||||
"""
|
||||
any constat such as {{ "foo" }}
|
||||
"""
|
||||
_fields = ('value',)
|
||||
|
||||
def as_const(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class Tuple(Literal):
|
||||
"""
|
||||
For loop unpacking and some other things like multiple arguments
|
||||
for subscripts.
|
||||
"""
|
||||
_fields = ('items',)
|
||||
|
||||
def as_const(self):
|
||||
return tuple(x.as_const() 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):
|
||||
return [x.as_const() for x in self.items]
|
||||
|
||||
|
||||
class Dict(Literal):
|
||||
"""
|
||||
any dict literal such as {{ {1: 2, 3: 4} }}
|
||||
"""
|
||||
_fields = ('items',)
|
||||
|
||||
def as_const(self):
|
||||
return dict(x.as_const() for x in self.items)
|
||||
|
||||
|
||||
class Pair(Helper):
|
||||
"""
|
||||
A key, value pair for dicts.
|
||||
"""
|
||||
_fields = ('key', 'value')
|
||||
|
||||
def as_const(self):
|
||||
return self.key.as_const(), self.value.as_const()
|
||||
|
||||
|
||||
class CondExpr(Expr):
|
||||
"""
|
||||
{{ foo if bar else baz }}
|
||||
"""
|
||||
_fields = ('test', 'expr1', 'expr2')
|
||||
|
||||
def as_const(self):
|
||||
if self.test.as_const():
|
||||
return self.expr1.as_const()
|
||||
return self.expr2.as_const()
|
||||
|
||||
|
||||
class Filter(Expr):
|
||||
"""
|
||||
{{ foo|bar|baz }}
|
||||
"""
|
||||
_fields = ('node', 'filters')
|
||||
|
||||
|
||||
class Test(Expr):
|
||||
"""
|
||||
{{ foo is lower }}
|
||||
"""
|
||||
_fields = ('node', 'name', 'args')
|
||||
|
||||
|
||||
class Call(Expr):
|
||||
"""
|
||||
{{ foo(bar) }}
|
||||
"""
|
||||
_fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||
|
||||
|
||||
class Subscript(Expr):
|
||||
"""
|
||||
{{ foo.bar }} and {{ foo['bar'] }} etc.
|
||||
"""
|
||||
_fields = ('node', 'arg')
|
||||
|
||||
def as_const(self):
|
||||
try:
|
||||
return self.node.as_const()[self.node.as_const()]
|
||||
except:
|
||||
raise Impossible()
|
||||
|
||||
def can_assign(self):
|
||||
return True
|
||||
|
||||
|
||||
class Slice(Expr):
|
||||
"""
|
||||
1:2:3 etc.
|
||||
"""
|
||||
_fields = ('start', 'stop', 'step')
|
||||
|
||||
|
||||
class Concat(Expr):
|
||||
"""
|
||||
For {{ foo ~ bar }}. Concatenates strings.
|
||||
"""
|
||||
_fields = ('nodes',)
|
||||
|
||||
def as_const(self):
|
||||
return ''.join(unicode(x.as_const()) for x in self.nodes)
|
||||
|
||||
|
||||
class Compare(Expr):
|
||||
"""
|
||||
{{ foo == bar }}, {{ foo >= bar }} etc.
|
||||
"""
|
||||
_fields = ('expr', 'ops')
|
||||
|
||||
|
||||
class Mul(BinExpr):
|
||||
"""
|
||||
{{ foo * bar }}
|
||||
"""
|
||||
operator = '*'
|
||||
|
||||
|
||||
class Div(BinExpr):
|
||||
"""
|
||||
{{ foo / bar }}
|
||||
"""
|
||||
operator = '/'
|
||||
|
||||
|
||||
class FloorDiv(BinExpr):
|
||||
"""
|
||||
{{ foo // bar }}
|
||||
"""
|
||||
operator = '//'
|
||||
|
||||
|
||||
class Add(BinExpr):
|
||||
"""
|
||||
{{ foo + bar }}
|
||||
"""
|
||||
operator = '+'
|
||||
|
||||
|
||||
class Sub(BinExpr):
|
||||
"""
|
||||
{{ foo - bar }}
|
||||
"""
|
||||
operator = '-'
|
||||
|
||||
|
||||
class Mod(BinExpr):
|
||||
"""
|
||||
{{ foo % bar }}
|
||||
"""
|
||||
operator = '%'
|
||||
|
||||
|
||||
class Pow(BinExpr):
|
||||
"""
|
||||
{{ foo ** bar }}
|
||||
"""
|
||||
operator = '**'
|
||||
|
||||
|
||||
class And(BinExpr):
|
||||
"""
|
||||
{{ foo and bar }}
|
||||
"""
|
||||
operator = 'and'
|
||||
|
||||
def as_const(self):
|
||||
return self.left.as_const() and self.right.as_const()
|
||||
|
||||
|
||||
class Or(BinExpr):
|
||||
"""
|
||||
{{ foo or bar }}
|
||||
"""
|
||||
operator = 'or'
|
||||
|
||||
def as_const(self):
|
||||
return self.left.as_const() or self.right.as_const()
|
||||
|
||||
|
||||
class Not(UnaryExpr):
|
||||
"""
|
||||
{{ not foo }}
|
||||
"""
|
||||
operator = 'not'
|
||||
|
||||
|
||||
class NegExpr(UnaryExpr):
|
||||
"""
|
||||
{{ -foo }}
|
||||
"""
|
||||
operator = '-'
|
||||
|
||||
|
||||
class PosExpr(UnaryExpr):
|
||||
"""
|
||||
{{ +foo }}
|
||||
"""
|
||||
operator = '+'
|
39
jinja2/parser.py
Normal file
39
jinja2/parser.py
Normal file
@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.parser
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Implements the template parser.
|
||||
|
||||
:copyright: 2008 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja import nodes
|
||||
from jinja.exceptions import TemplateSyntaxError
|
||||
|
||||
|
||||
__all__ = ['Parser']
|
||||
|
||||
|
||||
class Parser(object):
|
||||
"""
|
||||
The template parser class.
|
||||
|
||||
Transforms sourcecode into an abstract syntax tree.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, source, filename=None):
|
||||
self.environment = environment
|
||||
if isinstance(source, str):
|
||||
source = source.decode(environment.template_charset, 'ignore')
|
||||
if isinstance(filename, unicode):
|
||||
filename = filename.encode('utf-8')
|
||||
self.source = source
|
||||
self.filename = filename
|
||||
self.closed = False
|
||||
self.blocks = set()
|
||||
self.no_variable_block = self.environment.lexer.no_variable_block
|
||||
self.stream = environment.lexer.tokenize(source, filename)
|
||||
|
||||
def parse(self):
|
||||
pass
|
10
jinja2/utils.py
Normal file
10
jinja2/utils.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.utils
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Utility functions.
|
||||
|
||||
:copyright: 2008 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
Loading…
Reference in New Issue
Block a user