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:
Armin Ronacher 2008-03-31 14:18:49 +02:00
parent e074cd2d47
commit 07bc684ccd
30 changed files with 863 additions and 5084 deletions

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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.
"""

View File

@ -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)

View File

@ -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 }}
"""

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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 ('(', '<', '&lt;')]),
'|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '&gt;')])
)
)
_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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
if not quote:
return s
return s.replace('"', "&quot;")
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
}

View File

@ -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
View 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()

View File

@ -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
View 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
View 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.
"""

View File

@ -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'(<!--.*?-->|<[^>]*>)')

View File

@ -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
View 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
View 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
View 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
View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
"""
jinja2.utils
~~~~~~~~~~~~
Utility functions.
:copyright: 2008 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""